{
  "components": {
    "schemas": {
      "ContentBlock": {
        "additionalProperties": true,
        "description": "A non-text content block. Rare today; tools that attach images, resources, or widget payloads populate `data.content` with blocks of this shape.",
        "properties": {
          "type": {
            "example": "image",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "type": "object"
      },
      "ErrorResponse": {
        "description": "Returned on HTTP 4xx / 5xx (except 402 which uses `QuotaErrorResponse`). `error` is always a human-readable string safe to surface to end users.",
        "properties": {
          "error": {
            "description": "Human-readable error message.",
            "type": "string"
          },
          "is_error": {
            "const": true,
            "type": "boolean"
          },
          "structured_content": {
            "additionalProperties": true,
            "description": "Present when the underlying tool attached structured metadata to the error (e.g. account-selection lists).",
            "type": "object"
          },
          "success": {
            "const": false,
            "type": "boolean"
          },
          "tool": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "error",
          "tool"
        ],
        "type": "object"
      },
      "QuotaBlock": {
        "description": "Current quota state for the API key owner. Attached to every successful billable response under `data.quota`, and to 402 errors under `quota` (plus `upgrade_url`).",
        "properties": {
          "limit": {
            "description": "Tool call allowance for the current tier.",
            "example": 150,
            "type": "integer"
          },
          "period_end": {
            "description": "When the monthly counter resets (ISO date).",
            "example": "2026-05-01",
            "format": "date",
            "type": "string"
          },
          "tier": {
            "description": "Subscription tier of the account that owns the API key.",
            "enum": [
              "free",
              "plus",
              "pro",
              "max"
            ],
            "example": "plus",
            "type": "string"
          },
          "upgrade_url": {
            "description": "Upgrade link (present only on 402 quota errors).",
            "format": "uri",
            "type": "string"
          },
          "used": {
            "description": "Tool calls consumed this billing period.",
            "example": 42,
            "type": "integer"
          }
        },
        "required": [
          "used",
          "limit",
          "tier",
          "period_end"
        ],
        "type": "object"
      },
      "QuotaErrorResponse": {
        "description": "Returned on HTTP 402 when the monthly quota is exhausted. Identical to `ErrorResponse` but with an additional `quota` block.",
        "properties": {
          "error": {
            "type": "string"
          },
          "is_error": {
            "const": true,
            "type": "boolean"
          },
          "quota": {
            "$ref": "#/components/schemas/QuotaBlock"
          },
          "success": {
            "const": false,
            "type": "boolean"
          },
          "tool": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "error",
          "tool",
          "quota"
        ],
        "type": "object"
      },
      "SuccessResponse": {
        "description": "Returned on HTTP 200. `data.text` is the primary human-readable output. `data.quota` is always present for billable calls. `data.structured` is set only when the tool emits machine-parseable structured content. `data.content` is set only when the tool emits non-text content blocks.",
        "properties": {
          "data": {
            "properties": {
              "content": {
                "description": "Non-text content blocks (images, resources).",
                "items": {
                  "$ref": "#/components/schemas/ContentBlock"
                },
                "type": "array"
              },
              "quota": {
                "$ref": "#/components/schemas/QuotaBlock"
              },
              "structured": {
                "additionalProperties": true,
                "description": "Machine-parseable structured content when the tool provides it (e.g. account selection prompts, widget payloads).",
                "type": "object"
              },
              "text": {
                "description": "Human-readable output. Markdown-friendly for tools that emit formatted lists, tables, or recommendations.",
                "type": "string"
              }
            },
            "required": [
              "text"
            ],
            "type": "object"
          },
          "success": {
            "const": true,
            "type": "boolean"
          },
          "tool": {
            "description": "Echoed tool_name from the request URL.",
            "type": "string"
          }
        },
        "required": [
          "success",
          "data",
          "tool"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "bearerFormat": "API Key (sk_live_...)",
        "description": "API key from https://adspirer.ai/keys. Prefix `sk_live_`. Treat as a secret \u2014 never commit.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {
      "email": "support@adspirer.com",
      "name": "Adspirer Support"
    },
    "description": "REST endpoints for every Adspirer tool. Same surface as the MCP server, over plain HTTP for consumers that can't speak SSE (n8n, Zapier, Make, curl, any language's HTTP client).\n\n## Envelope\nEvery request wraps tool-specific input in an `arguments` object:\n```json\n{ \"arguments\": { <tool-specific fields> } }\n```\nEvery response wraps the result in either a success envelope (`success: true`, `data: {...}`) or an error envelope (`success: false`, `error: \"...\"`, `is_error: true`).\n\n## Authentication\nPass your API key as `Authorization: Bearer sk_live_...` on every request. Generate keys at https://adspirer.ai/keys.\n\n## Quota & billing\nEvery successful billable call decrements your monthly tool-call allowance. The current counter is attached to every 200 response under `data.quota`:\n```json\n\"quota\": { \"used\": 42, \"limit\": 150, \"tier\": \"plus\", \"period_end\": \"2026-05-01\" }\n```\nWhen the limit is hit, the API returns HTTP 402 with a full `quota` block including `upgrade_url`. Read-only diagnostic tools (`get_usage_status`, `list_connected_accounts`, `get_connections_status`) are exempt and never consume quota.\n\n## Idempotency\nWrite operations accept an `Idempotency-Key: <uuid>` header. A repeated call with the same key returns the cached result rather than executing twice. **Strongly recommended for n8n, Zapier, and any retry-prone client** \u2014 it prevents duplicate campaigns when networks misbehave. Generate a fresh UUID per logical operation (not per retry).\n\n## Multi-account users\nCustomers with multiple connected accounts on the same platform (e.g. an agency with 10 Meta ad accounts) must specify which account to use via `ad_account_id`, `customer_id`, `advertiser_id`, or `account_id` depending on the platform. Omitting it returns HTTP 400 with a list of valid account IDs. See `list_connected_accounts` to discover available IDs.\n\n## HTTP status codes\n- `200` \u2014 success (parse `data`)\n- `400` \u2014 tool-level error (surface `error` to users)\n- `401` \u2014 bad/missing API key\n- `402` \u2014 Adspirer quota exhausted\n- `404` \u2014 unknown tool name\n- `429` \u2014 upstream ad platform rate-limited us (Meta/Google/etc.) \u2014 retry with backoff\n- `500` \u2014 server error, report to support\n\n## Streaming\nThis endpoint is plain request-response JSON. There is **no SSE, no chunked streaming**. Safe to use from n8n Cloud's HTTP Request node, Zapier Webhooks, Make HTTP module, curl, and every mainstream HTTP library.",
    "title": "Adspirer REST API",
    "version": "1.0.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/tools/add_callout_extensions/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd callout extensions to a campaign.\n\nCallouts are short, non-clickable highlights that appear below your ad.\nThey communicate quick trust signals and differentiators.\n\n**IMPORTANT RULES (from google-ads-creative-guidelines.md):**\n- Max 25 characters each\n- 4-8 callouts recommended\n- Should NOT be CTAs (no \"click\", \"buy\", \"order\", \"call\")\n- Should NOT end with periods\n\n**GOOD EXAMPLES:**\n- \"Free Shipping\"\n- \"24/7 Support\"\n- \"No Code Required\"\n- \"30 Second Setup\"\n- \"Award Winning\"\n\n**BAD EXAMPLES (will be rejected):**\n- \"Click Now\" (CTA word)\n- \"Buy Today\" (CTA word)\n- \"Free Shipping.\" (ends with period)\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.\n- callouts: List of callout text strings (REQUIRED). Each max 25 chars.\n- customer_id: Optional Google Ads customer ID\n\n**Execution time:** 2-5 seconds\n\n**When to use:**\n- User wants to add callouts/highlights to their campaign\n- User asks about extensions or ad enhancements\n- After creating a campaign, suggest adding callouts\n\n**Example:**\nUser: \"Add some callouts to my campaign\"\nAgent:\n1. Uses list_campaigns to get campaign_id\n2. Uses add_callout_extensions with relevant callouts",
        "operationId": "execute_add_callout_extensions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "callouts": [
                    "string"
                  ],
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding callout extensions to a campaign.\n\nCallouts are short, non-clickable highlights that appear below your ad.\nThey communicate quick trust signals and differentiators.\n\nGOOD EXAMPLES: \"Free Shipping\", \"24/7 Support\", \"No Code Required\", \"30 Second Setup\"\nBAD EXAMPLES: \"Click Now\", \"Buy Today\" (these are CTAs, not allowed)\n\nFrom google-ads-creative-guidelines.md:\n- Max 25 characters each\n- 4-8 callouts recommended\n- Should NOT be CTAs (no \"click\", \"buy\", \"order\", \"call\")\n- Should NOT end with periods",
                    "properties": {
                      "callouts": {
                        "description": "List of callout text strings. Each max 25 characters. Example: ['Free Shipping', '24/7 Support', 'No Code Required']",
                        "items": {
                          "type": "string"
                        },
                        "title": "Callouts",
                        "type": "array"
                      },
                      "campaign_id": {
                        "description": "The campaign ID to add callouts to. Get from list_campaigns.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "callouts"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_callout_extensions)"
                  },
                  "success": true,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_callout_extensions",
                  "is_error": true,
                  "success": false,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_callout_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_callout_extensions"
      }
    },
    "/api/v1/tools/add_demandgen_ad_group/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd a new ad group with targeting and ads to an EXISTING Demand Gen campaign.\n\nUse this after create_demandgen_campaign to add additional ad groups. Each ad group can have:\n- **Different location targeting** (e.g., US cities vs India cities vs UAE cities)\n- **Different audience targeting** (e.g., marketing pros vs tech founders vs agency owners)\n- **Same or different ad creative** (headlines, descriptions, images, videos)\n\n**Reuse Existing Audiences:**\nTo attach an existing audience (from Google Ads UI or a previous campaign), pass:\n- audience_segments.existing_audience_id: numeric audience ID\n- audience_segments.existing_audience_resource_name: full resource name\n\n**Create New Audiences:**\nTo create a new audience from interest segments:\n- audience_segments.in_market_audience_ids: [80517, 80520]\n- audience_segments.affinity_audience_ids: [92948]\nUse search_audiences to find segment IDs.\n\n**Parameters:**\n- campaign_id: Demand Gen campaign ID (from create_demandgen_campaign or list_campaigns)\n- ad_group_name: Descriptive name\n- Same creative fields as create_demandgen_campaign (headlines, descriptions, images, etc.)\n- target_locations: Location targeting specific to this ad group\n- audience_segments: Audience targeting specific to this ad group\n\n**Execution time:** 10-20 seconds",
        "operationId": "execute_add_demandgen_ad_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_format": "multi_asset",
                  "ad_group_name": "string",
                  "business_name": "string",
                  "call_to_action": "string",
                  "campaign_id": "<campaign_id>",
                  "channels": [
                    "YOUTUBE",
                    "DISCOVER",
                    "GMAIL"
                  ],
                  "descriptions": [
                    "string"
                  ],
                  "final_url": "https://example.com",
                  "headlines": [
                    "string"
                  ],
                  "long_headlines": [
                    "string"
                  ],
                  "target_languages": [
                    "string"
                  ],
                  "target_locations": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for add_demandgen_ad_group tool.\n\nAdds a new ad group with targeting and ads to an existing Demand Gen campaign.\nEach ad group can have its own location targeting, audience targeting, and creative.",
                    "properties": {
                      "ad_format": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "multi_asset",
                        "description": "Ad format: 'multi_asset' (image ads, default), 'video_responsive' (video ads).",
                        "title": "Ad Format"
                      },
                      "ad_group_name": {
                        "description": "Name for the new ad group (e.g., 'US Metro - Marketing Pros')",
                        "title": "Ad Group Name",
                        "type": "string"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID from validate_and_prepare_assets.",
                        "title": "Asset Bundle Id"
                      },
                      "audience_segments": {
                        "anyOf": [
                          {
                            "additionalProperties": true,
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 audience_segments entirely. To REUSE an existing audience: {'existing_audience_id': 12345} or {'existing_audience_resource_name': 'customers/123/audiences/456'}. To CREATE new: {'in_market_audience_ids': [80463], 'affinity_audience_ids': [92913], 'custom_audience_ids': ['customers/123/customAudiences/456']}. Use search_audiences to find segment IDs.",
                        "title": "Audience Segments"
                      },
                      "business_name": {
                        "description": "Business name, max 25 characters.",
                        "maxLength": 25,
                        "title": "Business Name",
                        "type": "string"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "CTA label: LEARN_MORE, SHOP_NOW, SIGN_UP, SUBSCRIBE, DOWNLOAD, BOOK_NOW, etc.",
                        "title": "Call To Action"
                      },
                      "campaign_id": {
                        "description": "The Demand Gen campaign ID to add the ad group to. Get from list_campaigns or get_campaign_structure.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "channels": {
                        "anyOf": [
                          {
                            "additionalProperties": {
                              "type": "boolean"
                            },
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional channel controls for this ad group.",
                        "title": "Channels"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "description": "MUST BE JSON ARRAY: 1-5 descriptions, each max 90 characters.",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 5,
                        "minItems": 1,
                        "title": "Descriptions",
                        "type": "array"
                      },
                      "existing_images": {
                        "anyOf": [
                          {
                            "additionalProperties": {
                              "items": {
                                "type": "string"
                              },
                              "type": "array"
                            },
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing image asset resource names to reuse. Keys: marketing_images_landscape, marketing_images_square, marketing_images_portrait, logos_square.",
                        "title": "Existing Images"
                      },
                      "final_url": {
                        "description": "Landing page URL. Example: 'https://example.com/product'",
                        "title": "Final Url",
                        "type": "string"
                      },
                      "headlines": {
                        "description": "MUST BE JSON ARRAY: 1-5 headlines, each max 40 characters.",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 5,
                        "minItems": 1,
                        "title": "Headlines",
                        "type": "array"
                      },
                      "logo_asset_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Logo image asset ID from discover_existing_assets.",
                        "title": "Logo Asset Id"
                      },
                      "logo_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New logo images as [{\"url\": \"...\", \"name\": \"logo1\"}].",
                        "title": "Logo Images"
                      },
                      "long_headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "maxItems": 5,
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional 1-5 long headlines, max 90 chars each. For video_responsive format.",
                        "title": "Long Headlines"
                      },
                      "marketing_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Landscape images (1.91:1) as [{\"url\": \"...\", \"name\": \"img1\"}].",
                        "title": "Marketing Images"
                      },
                      "portrait_marketing_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Portrait images (4:5) as [{\"url\": \"...\", \"name\": \"img1\"}].",
                        "title": "Portrait Marketing Images"
                      },
                      "square_marketing_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Square images (1:1) as [{\"url\": \"...\", \"name\": \"img1\"}].",
                        "title": "Square Marketing Images"
                      },
                      "target_languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Language targets as ISO codes (e.g., ['en']). Defaults to English.",
                        "title": "Target Languages"
                      },
                      "target_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Geographic targets for THIS ad group (e.g., ['New York', 'Bangalore']). Each ad group can target different locations.",
                        "title": "Target Locations"
                      },
                      "youtube_video_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "YouTube video IDs for video_responsive format. 1-5 videos.",
                        "title": "Youtube Video Ids"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "ad_group_name",
                      "final_url",
                      "business_name",
                      "headlines",
                      "descriptions"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_demandgen_ad_group)"
                  },
                  "success": true,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_demandgen_ad_group",
                  "is_error": true,
                  "success": false,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_demandgen_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_demandgen_ad_group"
      }
    },
    "/api/v1/tools/add_keywords/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd keywords to an existing ad group.\n\n\u26a0\ufe0f **CRITICAL: Run `research_keywords` BEFORE using this tool!**\n\n**MANDATORY WORKFLOW:**\n1. Call `get_campaign_structure` to get ad_group_id and see existing keywords\n2. Call `research_keywords` with business context to get quality keyword suggestions\n3. Show keyword suggestions to user with CPC, search volume, intent level\n4. Get user approval on which keywords to add\n5. ONLY THEN call this tool with approved keywords\n\n**Why research first?**\n- Ensures keywords have real search volume\n- Shows CPC costs so user understands budget impact\n- Provides HIGH/MEDIUM/LOW intent classification\n- Prevents adding low-quality or irrelevant keywords\n\n**Requires:**\n- ad_group_id: Get from get_campaign_structure (REQUIRED)\n- keywords: List of keywords to add (from research_keywords results)\n\n**Each keyword needs:**\n- text: The keyword phrase (e.g., \"buy running shoes online\")\n- match_type: EXACT, PHRASE, or BROAD (default: BROAD)\n- cpc_bid_micros: Optional bid override (1 USD = 1,000,000 micros)\n\n**Match Type Guide:**\n- BROAD: Ads show for related searches (widest reach) - Google's 2025 recommendation\n- PHRASE: Ads show when query contains keyword phrase\n- EXACT: Ads show only for exact query or close variants\n\n**IMPORTANT:**\n- Keyword text and match_type CANNOT be changed after creation\n- To \"change\" a keyword, remove the old one and add a new one\n- Get ad_group_id from get_campaign_structure first\n\n**Execution time:** 2-4 seconds\n\n**Example Workflow:**\nUser: \"Add more keywords to my plumbing campaign\"\n\nAgent Steps:\n1. Call get_campaign_structure \u2192 get ad_group_id, see existing keywords\n2. Call research_keywords with business_description=\"plumbing services\"\n3. Show user: \"I found these keyword suggestions from Keyword Planner:\n   - emergency plumber near me (HIGH intent, $45 CPC, 12K searches/mo)\n   - 24 hour plumber (HIGH intent, $38 CPC, 8K searches/mo)\n   Which would you like to add?\"\n4. User selects keywords\n5. Call add_keywords with selected keywords",
        "operationId": "execute_add_keywords",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "customer_id": "string",
                  "keywords": [
                    {
                      "cpc_bid_micros": 500000,
                      "match_type": "PHRASE",
                      "text": "running shoes"
                    },
                    {
                      "match_type": "EXACT",
                      "text": "nike air max"
                    }
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding keywords to an ad group",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID to add keywords to. Use get_campaign_structure first to find ad_group_id.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "keywords": {
                        "description": "List of keywords to add. Each keyword: {text, match_type (EXACT/PHRASE/BROAD), cpc_bid_micros (optional)}",
                        "items": {
                          "additionalProperties": true,
                          "type": "object"
                        },
                        "title": "Keywords",
                        "type": "array"
                      }
                    },
                    "required": [
                      "ad_group_id",
                      "keywords"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_keywords)"
                  },
                  "success": true,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_keywords",
                  "is_error": true,
                  "success": false,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_keywords"
      }
    },
    "/api/v1/tools/add_linkedin_campaign_to_group/execute": {
      "post": {
        "description": "**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.\n\nLinkedIn hierarchy: Campaign Group \u2192 Campaign \u2192 Creative.\n- Different audiences = different Campaigns in the SAME Campaign Group.\n- Different creatives = different Creatives in the SAME Campaign.\n\n**Common scenarios:**\n- Audience testing: Same ad format, different targeting per campaign\n- Geographic split: Same creative, different locations per campaign\n- Multi-market: Same product, different countries\n- Scaling: Adding new audiences to existing campaign group\n\n**CRITICAL:** Each create_linkedin_*_campaign creates a new Campaign Group (unless campaign_group_id is passed).\nTo add more campaigns to the SAME group, you MUST use this tool or pass campaign_group_id to create tools.\nNEVER call create_linkedin_*_campaign without campaign_group_id for the same group.\n\n**KEY DISTINCTION:**\n- Different audience/targeting \u2192 use THIS tool (new campaign in same group)\n- Different copy/creative, same audience \u2192 use add_linkedin_creative / add_linkedin_video_creative instead\n\n**Supports all 4 ad types:** image, video, carousel, text.\nEach campaign has INDEPENDENT: targeting, budget, schedule.\nShared from campaign group: objective, group name.\n\n**Workflow:**\n1. Create initial campaign \u2192 get campaign_group_id + campaign_id\n2. Call this tool with campaign_group_id for each additional audience\n3. Use add_linkedin_creative to add more creatives within any campaign\n\nExecution time: 15-120 seconds (depends on ad type)",
        "operationId": "execute_add_linkedin_campaign_to_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_type": "string",
                  "asset_bundle_id": "string",
                  "campaign_group_id": "string",
                  "campaign_name": "string",
                  "cards": [
                    {
                      "description": "Actionable insights for 2026",
                      "headline": "Download our whitepaper",
                      "image_url": "https://example.com/cover.jpg",
                      "landing_page_url": "https://example.com/whitepaper"
                    }
                  ],
                  "daily_budget": 10.0,
                  "image_urn": "string",
                  "landing_page_url": "https://example.com",
                  "locations": [
                    "string"
                  ],
                  "organization_id": "string",
                  "text_headline": "string",
                  "video_url": "string",
                  "video_urn": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a new campaign (with different targeting) to an existing campaign group.\nThis is the LinkedIn equivalent of Meta's add_meta_ad_set tool.\n\nLinkedIn hierarchy: Campaign Group \u2192 Campaign \u2192 Creative\nThis tool creates: Campaign + first Creative under an EXISTING Campaign Group.",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional).",
                        "title": "Account Id"
                      },
                      "ad_type": {
                        "description": "Type of ad for this campaign: 'image', 'video', 'carousel', or 'text'.",
                        "title": "Ad Type",
                        "type": "string"
                      },
                      "age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Age range URNs.",
                        "title": "Age Ranges"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID from validate_and_prepare_linkedin_assets (for new image uploads).",
                        "title": "Asset Bundle Id"
                      },
                      "buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Buyer group URNs (API 2026-03+).",
                        "title": "Buyer Groups"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "CTA button label. Default: LEARN_MORE.",
                        "title": "Call To Action"
                      },
                      "campaign_group_id": {
                        "description": "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.",
                        "title": "Campaign Group Id",
                        "type": "string"
                      },
                      "campaign_name": {
                        "description": "Name for this new campaign (auto-suffixed with timestamp).",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "cards": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel cards (2-10). Each: {image_urn or image_url, headline (max 45), landing_page_url}.",
                        "title": "Cards"
                      },
                      "company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Company size URNs.",
                        "title": "Company Sizes"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for the first creative. E.g. 'US Audience - Ad 1'.",
                        "title": "Creative Name"
                      },
                      "currency": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "USD",
                        "description": "Currency code. Default: USD.",
                        "title": "Currency"
                      },
                      "daily_budget": {
                        "description": "Daily budget for this campaign in account currency (minimum $10).",
                        "minimum": 10.0,
                        "title": "Daily Budget",
                        "type": "number"
                      },
                      "degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Degree URNs.",
                        "title": "Degrees"
                      },
                      "employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Employer URNs.",
                        "title": "Employers"
                      },
                      "fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Field of study URNs.",
                        "title": "Fields Of Study"
                      },
                      "followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Followed company URNs.",
                        "title": "Followed Companies"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Gender URNs.",
                        "title": "Genders"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 70,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Ad headline (up to 70 chars). Required for image and video ad types.",
                        "title": "Headline"
                      },
                      "image_urn": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Image URN for image ads. From discover_linkedin_assets or validate_and_prepare_linkedin_assets.",
                        "title": "Image Urn"
                      },
                      "industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Industry URNs.",
                        "title": "Industries"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Interest URNs.",
                        "title": "Interests"
                      },
                      "introductory_text": {
                        "anyOf": [
                          {
                            "maxLength": 600,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Main ad text (up to 600 chars for image/video, 255 for carousel). Required for image, video, and carousel ad types.",
                        "title": "Introductory Text"
                      },
                      "job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Job function URNs.",
                        "title": "Job Functions"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Job title URNs.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Destination URL (must be HTTPS).",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "locations": {
                        "description": "Location URNs for this campaign's targeting. Required.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Locations",
                        "type": "array"
                      },
                      "member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Member behavior URNs.",
                        "title": "Member Behaviors"
                      },
                      "member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn group URNs.",
                        "title": "Member Groups"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign objective. If not provided, inherits from the campaign group's objective. Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION, VIDEO_VIEW, JOB_APPLICANT.",
                        "title": "Objective"
                      },
                      "organization_id": {
                        "description": "LinkedIn Organization (Company Page) ID for ad authoring.",
                        "title": "Organization Id",
                        "type": "string"
                      },
                      "schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "School URNs.",
                        "title": "Schools"
                      },
                      "seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Seniority URNs.",
                        "title": "Seniorities"
                      },
                      "skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Skill URNs.",
                        "title": "Skills"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "PAUSED",
                        "description": "Campaign status. Default: PAUSED.",
                        "title": "Status"
                      },
                      "text_description": {
                        "anyOf": [
                          {
                            "maxLength": 75,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Text ad description (max 75 chars). Required for ad_type='text'.",
                        "title": "Text Description"
                      },
                      "text_headline": {
                        "anyOf": [
                          {
                            "maxLength": 25,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Text ad headline (max 25 chars). Required for ad_type='text'.",
                        "title": "Text Headline"
                      },
                      "video_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Public URL to upload video from (MP4, max 500MB).",
                        "title": "Video Url"
                      },
                      "video_urn": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Video URN for video ads. From discover_linkedin_assets.",
                        "title": "Video Urn"
                      },
                      "years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Years of experience URNs.",
                        "title": "Years Of Experience"
                      }
                    },
                    "required": [
                      "campaign_group_id",
                      "campaign_name",
                      "daily_budget",
                      "organization_id",
                      "ad_type",
                      "landing_page_url",
                      "locations"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_linkedin_campaign_to_group)"
                  },
                  "success": true,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_linkedin_campaign_to_group",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_campaign_to_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to add a new campaign with DIFFERENT targeting/audience to an EXISTING campaign group [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_linkedin_campaign_to_group"
      }
    },
    "/api/v1/tools/add_linkedin_carousel_creative/execute": {
      "post": {
        "description": "User wants to add another carousel ad variation to an existing carousel campaign.\n\nAdd a new carousel creative to a carousel campaign for A/B testing.\n\nLinkedIn recommends 4+ carousel ad variations per campaign for optimal performance.\n\n**DO NOT USE to create a new campaign.** For new campaigns:\n- Image \u2192 create_linkedin_image_campaign\n- Video \u2192 create_linkedin_video_campaign\n- Carousel \u2192 create_linkedin_carousel_campaign\n- Text \u2192 create_linkedin_text_campaign\n\n**DO NOT USE for different audiences.** For different targeting:\n\u2192 Use add_linkedin_campaign_to_group instead.\n\nRequired Parameters:\n- campaign_id: Carousel campaign to add creative to\n- organization_id: Organization for ad authoring\n- cards: Array of 2-10 carousel cards (image_urn/url, headline, landing_page_url)\n- introductory_text: Main ad text (max 255 chars)\n- landing_page_url: Overall landing page URL\n\nOptional Parameters:\n- creative_name: Descriptive name (e.g. \"Product Showcase - Carousel Ad 2\")\n- call_to_action: CTA label (default: LEARN_MORE)\n- account_id: LinkedIn Ad Account ID\n\nAD POLICY: NEVER use \"LinkedIn\" in ad copy.\n\nExecution time: 5-15 seconds",
        "operationId": "execute_add_linkedin_carousel_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "call_to_action": "LEARN_MORE",
                  "campaign_id": "<campaign_id>",
                  "cards": [
                    {
                      "description": "Explore the full lineup",
                      "headline": "Our product family",
                      "image_url": "https://example.com/family.jpg",
                      "landing_page_url": "https://example.com/products"
                    }
                  ],
                  "creative_name": "string",
                  "introductory_text": "string",
                  "landing_page_url": "https://example.com",
                  "organization_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a carousel creative to an existing carousel campaign.",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional, auto-resolved if omitted).",
                        "title": "Account Id"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "CTA button label. Default: LEARN_MORE.",
                        "title": "Call To Action"
                      },
                      "campaign_id": {
                        "description": "Carousel campaign ID to add creative to.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "cards": {
                        "description": "Carousel cards (2-10). Each card: {image_urn or image_url, headline (max 45 chars), landing_page_url}.",
                        "items": {
                          "additionalProperties": true,
                          "type": "object"
                        },
                        "maxItems": 10,
                        "minItems": 2,
                        "title": "Cards",
                        "type": "array"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for this creative. E.g. 'Product Showcase - Carousel Ad 2'.",
                        "title": "Creative Name"
                      },
                      "introductory_text": {
                        "description": "Main ad text (up to 255 characters for carousel ads).",
                        "maxLength": 255,
                        "title": "Introductory Text",
                        "type": "string"
                      },
                      "landing_page_url": {
                        "description": "Overall carousel landing page URL (must be HTTPS).",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "organization_id": {
                        "description": "Organization ID for ad authoring.",
                        "title": "Organization Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "organization_id",
                      "cards",
                      "introductory_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_linkedin_carousel_creative)"
                  },
                  "success": true,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_linkedin_carousel_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_carousel_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to add another carousel ad variation to an existing carousel campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_linkedin_carousel_creative"
      }
    },
    "/api/v1/tools/add_linkedin_creative/execute": {
      "post": {
        "description": "**USE THIS TOOL WHEN:** User wants to add another IMAGE ad/creative to an EXISTING LinkedIn image campaign.\n\n**DO NOT USE to create a new campaign.** For new campaigns:\n- Image \u2192 `create_linkedin_image_campaign`\n- Video \u2192 `create_linkedin_video_campaign`\n- Carousel \u2192 `create_linkedin_carousel_campaign`\n- Text \u2192 `create_linkedin_text_campaign`\n\n**DO NOT USE for different audiences/targeting.** For different audiences:\n\u2192 Use `add_linkedin_campaign_to_group` instead (creates a new campaign in the same group).\n\n**KEY DISTINCTION:**\n- Same audience, different creative/copy \u2192 use THIS tool\n- Different audience/targeting \u2192 use `add_linkedin_campaign_to_group` instead\n\n**Common scenarios:**\n- A/B test ad copy: Different headlines, introductory text, or CTAs\n- A/B test images: Different images for same audience\n- LinkedIn recommends 4+ ads per campaign for optimal performance\n\nRequired Parameters:\n- campaign_id: Campaign to add creative to\n- organization_id: Organization for ad authoring\n- image_urn: Image asset URN\n- introductory_text: Main ad copy\n- landing_page_url: Destination URL\n\nAD POLICY: NEVER use \"LinkedIn\" in ad copy. NEVER mention competitor platforms.\n\nExecution time: 5-10 seconds",
        "operationId": "execute_add_linkedin_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "call_to_action": "LEARN_MORE",
                  "campaign_id": "<campaign_id>",
                  "creative_name": "string",
                  "headline": "string",
                  "image_urn": "string",
                  "introductory_text": "string",
                  "landing_page_url": "https://example.com",
                  "organization_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a new creative to a campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "Call-to-action button label",
                        "title": "Call To Action"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to add creative to",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for this creative (shown in Campaign Manager UI). E.g. 'Summer Sale - Ad 2'",
                        "title": "Creative Name"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 70,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Ad headline (up to 70 characters)",
                        "title": "Headline"
                      },
                      "image_urn": {
                        "description": "Image asset URN from discover_linkedin_assets or validate_and_prepare_linkedin_assets",
                        "title": "Image Urn",
                        "type": "string"
                      },
                      "introductory_text": {
                        "description": "Main ad text/copy (up to 600 characters)",
                        "maxLength": 600,
                        "title": "Introductory Text",
                        "type": "string"
                      },
                      "landing_page_url": {
                        "description": "Destination URL where users will be directed",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "organization_id": {
                        "description": "Organization ID for ad authoring",
                        "title": "Organization Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "organization_id",
                      "image_urn",
                      "introductory_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_linkedin_creative)"
                  },
                  "success": true,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_linkedin_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to add another IMAGE ad/creative to an EXISTING LinkedIn image campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_linkedin_creative"
      }
    },
    "/api/v1/tools/add_linkedin_text_creative/execute": {
      "post": {
        "description": "**USE THIS TOOL WHEN:** User wants to add another text ad to an EXISTING TEXT_AD campaign.\n\n**DO NOT USE to create a new campaign.** For new campaigns \u2192 `create_linkedin_text_campaign`.\n**DO NOT USE for image/video/carousel campaigns** \u2192 use `add_linkedin_creative` or `add_linkedin_video_creative`.\n**DO NOT USE for different audiences** \u2192 use `add_linkedin_campaign_to_group` instead.\n\n**KEY DISTINCTION:**\n- Same audience, different headline/description \u2192 use THIS tool\n- Different audience/targeting \u2192 use `add_linkedin_campaign_to_group`\n\nLinkedIn recommends 3-4 text ad variations per campaign.\n\nRequired Parameters:\n- campaign_id: TEXT_AD campaign to add creative to\n- headline: Text ad headline (max 25 chars)\n- description: Text ad description (max 75 chars)\n- landing_page_url: Destination URL (HTTPS)\n\nAD POLICY: NEVER use \"LinkedIn\" in headline or description.\n\nExecution time: 3-5 seconds",
        "operationId": "execute_add_linkedin_text_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "creative_name": "string",
                  "description": "string",
                  "headline": "string",
                  "image_urn": "string",
                  "landing_page_url": "https://example.com"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a text ad creative to a TEXT_AD campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional, auto-resolved if omitted)",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to add the text ad creative to",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for this creative (shown in Campaign Manager UI). E.g. 'B2B Leads - Ad 2'",
                        "title": "Creative Name"
                      },
                      "description": {
                        "description": "Text ad description (max 75 characters)",
                        "maxLength": 75,
                        "title": "Description",
                        "type": "string"
                      },
                      "headline": {
                        "description": "Text ad headline (max 25 characters)",
                        "maxLength": 25,
                        "title": "Headline",
                        "type": "string"
                      },
                      "image_urn": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional 100x100 logo/image URN for the text ad (JPG/PNG, max 2MB). Improves CTR.",
                        "title": "Image Urn"
                      },
                      "landing_page_url": {
                        "description": "Destination URL (must start with https://)",
                        "title": "Landing Page Url",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "headline",
                      "description",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_linkedin_text_creative)"
                  },
                  "success": true,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_linkedin_text_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_text_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to add another text ad to an EXISTING TEXT_AD campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_linkedin_text_creative"
      }
    },
    "/api/v1/tools/add_linkedin_video_creative/execute": {
      "post": {
        "description": "**USE THIS TOOL WHEN:** User wants to add another video ad to an EXISTING LinkedIn video campaign.\n\n**DO NOT USE to create a new campaign.** For new campaigns \u2192 `create_linkedin_video_campaign`.\n**DO NOT USE for image campaigns** \u2192 use `add_linkedin_creative` instead.\n**DO NOT USE for text ads** \u2192 use `add_linkedin_text_creative` instead.\n**DO NOT USE for different audiences** \u2192 use `add_linkedin_campaign_to_group` instead.\n\n**KEY DISTINCTION:**\n- Same audience, different video/copy \u2192 use THIS tool\n- Different audience/targeting \u2192 use `add_linkedin_campaign_to_group`\n\nLinkedIn recommends 3-4 video ad variations per campaign.\n\nRequired Parameters:\n- campaign_id: Video campaign to add creative to\n- organization_id: Organization for ad authoring\n- video_urn: Video asset URN (urn:li:video:...)\n- introductory_text: Main ad copy (max 600 chars)\n- landing_page_url: Destination URL (HTTPS)\n\nAD POLICY: NEVER use \"LinkedIn\" in ad copy.\n\nExecution time: 5-10 seconds",
        "operationId": "execute_add_linkedin_video_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "call_to_action": "LEARN_MORE",
                  "campaign_id": "<campaign_id>",
                  "creative_name": "string",
                  "headline": "string",
                  "introductory_text": "string",
                  "landing_page_url": "https://example.com",
                  "organization_id": "string",
                  "video_urn": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a video creative to a video campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional, auto-resolved if omitted)",
                        "title": "Account Id"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "Call-to-action button label",
                        "title": "Call To Action"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to add the video creative to",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for this creative (shown in Campaign Manager UI). E.g. 'Product Demo - Ad 2'",
                        "title": "Creative Name"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 70,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Ad headline (up to 70 characters)",
                        "title": "Headline"
                      },
                      "introductory_text": {
                        "description": "Main ad text/copy (up to 600 characters)",
                        "maxLength": 600,
                        "title": "Introductory Text",
                        "type": "string"
                      },
                      "landing_page_url": {
                        "description": "Destination URL (must start with https://)",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "organization_id": {
                        "description": "Organization ID for ad authoring",
                        "title": "Organization Id",
                        "type": "string"
                      },
                      "video_urn": {
                        "description": "Video asset URN (urn:li:video:...) from discover_linkedin_assets or video upload",
                        "title": "Video Urn",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "organization_id",
                      "video_urn",
                      "introductory_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_linkedin_video_creative)"
                  },
                  "success": true,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_linkedin_video_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_linkedin_video_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to add another video ad to an EXISTING LinkedIn video campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_linkedin_video_creative"
      }
    },
    "/api/v1/tools/add_meta_ad/execute": {
      "post": {
        "description": "User wants to add another ad/creative variation to an EXISTING ad set.\n\nDO NOT USE to create a new campaign. For new campaigns:\n- Video campaign - `create_meta_video_campaign`\n- Image campaign - `create_meta_image_campaign`\n- Carousel campaign - `create_meta_carousel_campaign`\n\nCommon scenarios:\n- A/B test ad copy: Different headlines, primary text, or CTAs for the same audience\n- A/B test creatives: Different images or videos for the same audience\n- Format testing within one audience: Image vs video ad in the same ad set\n- Dynamic Creative (DCO): Multiple images + text variations in one ad \u2014 Meta auto-optimizes\n\nKEY DISTINCTION \u2014 add_meta_ad vs add_meta_ad_set:\n- Same audience, different creative/copy - use THIS tool (add_meta_ad)\n- Different audience/targeting - use `add_meta_ad_set` instead (creates a new ad set)\n\nAds in the same ad set SHARE: targeting, budget, schedule, pixel tracking.\nEach ad has its OWN: creative (image/video/carousel), headline, primary text, CTA, landing page.\n\nIMPORTANT: 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.\n\nSupports all 3 ad types + Dynamic Creative:\n- `image`: Different image + copy variation\n- `video`: Different video + copy variation\n- `carousel`: Different card set + copy variation\n- **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.\n\nDynamic Creative Optimization (DCO) workflow:\n1. Create ad set with `is_dynamic_creative=true` via `add_meta_ad_set`\n2. Call this tool with `image_urls` (list of 2-10 image URLs), plus optional `headlines`, `primary_texts`, `descriptions` arrays\n3. Meta will test all image \u00d7 headline \u00d7 text combinations and optimize\n4. DCO ad sets support only 1 ad \u2014 if the ad set already has is_dynamic_creative enabled, this tool auto-detects it\nNote: Cannot mix image_urls (DCO) with image_url (single image) or placement-specific images.\n\nStandard workflow:\n1. Get ad_set_id from prior campaign creation or `list_meta_ad_sets`\n2. Write variant ad copy (different headline/primary_text/image)\n3. Call this tool with ad_set_id + new creative + copy",
        "operationId": "execute_add_meta_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_set_id": "string",
                  "ad_type": "string",
                  "asset_bundle_id": "string",
                  "existing_image_hash": "string",
                  "image_url": "string",
                  "landing_page_url": "https://example.com",
                  "primary_text": "string",
                  "right_column_image_url": "string",
                  "story_image_url": "string",
                  "video_url": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a new ad to an existing ad set",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta ad account ID",
                        "title": "Ad Account Id"
                      },
                      "ad_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom name for the ad. Also accepts 'name' as alias.",
                        "title": "Ad Name"
                      },
                      "ad_set_id": {
                        "description": "The Meta Ad Set ID to add the ad to (required)",
                        "title": "Ad Set Id",
                        "type": "string"
                      },
                      "ad_type": {
                        "description": "Type of ad: 'image', 'video', or 'carousel'",
                        "title": "Ad Type",
                        "type": "string"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID",
                        "title": "Asset Bundle Id"
                      },
                      "call_to_action": {
                        "default": "LEARN_MORE",
                        "description": "CTA button",
                        "title": "Call To Action",
                        "type": "string"
                      },
                      "cards": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel cards (2-10)",
                        "title": "Cards"
                      },
                      "description": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Description (max 255 chars, recommended 30)",
                        "title": "Description"
                      },
                      "descriptions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of description variations for Dynamic Creative (up to 5). Used with image_urls for DCO.",
                        "title": "Descriptions"
                      },
                      "existing_image_hash": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing image hash",
                        "title": "Existing Image Hash"
                      },
                      "existing_video_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing video ID",
                        "title": "Existing Video Id"
                      },
                      "facebook_page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook Page ID",
                        "title": "Facebook Page Id"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Headline (max 255 chars, recommended 40)",
                        "title": "Headline"
                      },
                      "headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of headline variations for Dynamic Creative (up to 5). Used with image_urls for DCO.",
                        "title": "Headlines"
                      },
                      "image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Image URL (for image type)",
                        "title": "Image Url"
                      },
                      "image_urls": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Image Urls"
                      },
                      "instagram_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram account ID",
                        "title": "Instagram Account Id"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL (HTTPS)",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "lead_form_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lead form ID for OUTCOME_LEADS campaigns. If not provided, auto-fetched from the ad set's campaign.",
                        "title": "Lead Form Id"
                      },
                      "multi_advertiser": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Set to false to opt out of Meta's multi-advertiser ad format at the creative level. Default (None) = Meta's default behavior (enrolled).",
                        "title": "Multi Advertiser"
                      },
                      "primary_text": {
                        "description": "Primary ad text (max 2200 chars, recommended 125). Supports emojis and line breaks.",
                        "maxLength": 2200,
                        "title": "Primary Text",
                        "type": "string"
                      },
                      "primary_texts": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of primary text variations for Dynamic Creative (up to 5). Used with image_urls for DCO.",
                        "title": "Primary Texts"
                      },
                      "right_column_image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Image URL for Right Column (1.91:1, recommended 1200x628px). Uses asset_feed_spec for placement-specific creatives.",
                        "title": "Right Column Image Url"
                      },
                      "story_image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Image URL for Stories/Reels (9:16, recommended 1080x1920px). Uses asset_feed_spec for placement-specific creatives.",
                        "title": "Story Image Url"
                      },
                      "thumbnail_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom thumbnail URL",
                        "title": "Thumbnail Url"
                      },
                      "video_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Video URL (for video type)",
                        "title": "Video Url"
                      }
                    },
                    "required": [
                      "ad_set_id",
                      "ad_type",
                      "primary_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_meta_ad)"
                  },
                  "success": true,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_meta_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to add another ad/creative variation to an EXISTING ad set [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_meta_ad"
      }
    },
    "/api/v1/tools/add_meta_ad_set/execute": {
      "post": {
        "description": "User wants to add a new ad set to an EXISTING campaign.\n\nCommon scenarios:\n- Audience testing: Same ad format, different targeting/interests per ad set (e.g., \"4 ad sets with different interests\")\n- Multi-format: Different ad types (image + video + carousel) under one campaign\n- Budget split: Same creative, different budgets per audience segment\n- Scaling: Adding new audiences/markets to an existing campaign\n- Geographic split: Same ad, different locations per ad set\n- Dynamic Creative (DCO): Set is_dynamic_creative=true, then use add_meta_ad with image_urls/headlines/primary_texts\n\nCRITICAL: Each `create_meta_*_campaign` creates a NEW campaign.\nTo add more ad sets to the SAME campaign, you MUST use this tool.\nNEVER call create_meta_*_campaign again for the same campaign.\n\nIMPORTANT: You need a campaign_id from a previously created campaign. Use `get_meta_campaign_details` first if you don't have the campaign_id.\n\nSupports all 3 ad types:\n- `image`: Provide image_url, existing_image_hash, or asset_bundle_id\n- `video`: Provide video_url or existing_video_id\n- `carousel`: Provide cards array (2-10 cards)\n\nDynamic Creative Optimization (DCO):\n- Set `is_dynamic_creative=true` when creating the ad set\n- Then use `add_meta_ad` with `image_urls` (2-10), `headlines` (up to 5), `primary_texts` (up to 5) to add a DCO ad\n- Meta automatically tests all asset combinations and optimizes delivery\n- DCO ad sets support only 1 ad \u2014 one ad with multiple asset variations\n\nEach ad set has INDEPENDENT: targeting, budget, schedule, pixel/conversion tracking.\nShared from campaign: objective, campaign name.\n\n**CBO (Advantage Campaign Budget) campaigns:**\nIf the campaign uses CBO (campaign_budget_optimization=true when created), you MUST:\n- Set `campaign_budget_optimization: true` on this tool call\n- Do NOT set `budget_daily` (budget is managed at campaign level)\n- Optionally set `daily_min_spend_target` and `daily_spend_cap` to control spend distribution\nIf the campaign does NOT use CBO, `budget_daily` is required as usual.\n\nWorkflow:\n1. Create initial campaign with `create_meta_*_campaign` - get campaign_id\n2. Call this tool with the campaign_id for each additional ad set\n3. Use `add_meta_ad` to add more ad copies/creatives within any ad set",
        "operationId": "execute_add_meta_ad_set",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_type": "string",
                  "budget_daily": 1.0,
                  "campaign_budget_optimization": false,
                  "campaign_id": "<campaign_id>",
                  "daily_min_spend_target": 1.0,
                  "daily_spend_cap": 1.0,
                  "existing_image_hash": "string",
                  "image_url": "string",
                  "landing_page_url": "https://example.com",
                  "primary_text": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding a new ad set to an existing campaign",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta ad account ID",
                        "title": "Ad Account Id"
                      },
                      "ad_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom name for the ad created inside the ad set",
                        "title": "Ad Name"
                      },
                      "ad_set_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom name for the ad set",
                        "title": "Ad Set Name"
                      },
                      "ad_type": {
                        "description": "Type of ad: 'image', 'video', or 'carousel'",
                        "title": "Ad Type",
                        "type": "string"
                      },
                      "age_max": {
                        "default": 65,
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Max",
                        "type": "integer"
                      },
                      "age_min": {
                        "default": 18,
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Min",
                        "type": "integer"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID from validate_and_prepare_meta_assets (for image ad_type)",
                        "title": "Asset Bundle Id"
                      },
                      "behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "title": "Behaviors"
                      },
                      "budget_daily": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Daily budget in USD for this ad set (minimum $1). Required for non-CBO campaigns. Do NOT set for CBO campaigns \u2014 budget is managed at campaign level. Use daily_min_spend_target/daily_spend_cap instead.",
                        "title": "Budget Daily"
                      },
                      "budget_lifetime": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Budget Lifetime"
                      },
                      "call_to_action": {
                        "default": "LEARN_MORE",
                        "description": "CTA button: LEARN_MORE, SHOP_NOW, SIGN_UP, etc.",
                        "title": "Call To Action",
                        "type": "string"
                      },
                      "campaign_budget_optimization": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": false,
                        "description": "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.",
                        "title": "Campaign Budget Optimization"
                      },
                      "campaign_id": {
                        "description": "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.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "cards": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel cards (2-10). Each: {image_url, headline, landing_page_url, description?, call_to_action?}",
                        "title": "Cards"
                      },
                      "custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "title": "Custom Audiences"
                      },
                      "custom_conversion_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored \u2014 Meta infers the event type from the custom conversion.",
                        "title": "Custom Conversion Id"
                      },
                      "daily_min_spend_target": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "CBO only: minimum daily spend for this ad set in account currency.",
                        "title": "Daily Min Spend Target"
                      },
                      "daily_spend_cap": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "CBO only: maximum daily spend cap for this ad set in account currency.",
                        "title": "Daily Spend Cap"
                      },
                      "description": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Description (max 255 chars, recommended 30)",
                        "title": "Description"
                      },
                      "destination_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Destination Type"
                      },
                      "end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "End Time"
                      },
                      "excluded_custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "title": "Excluded Custom Audiences"
                      },
                      "existing_image_hash": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing Meta image hash (for image ad_type)",
                        "title": "Existing Image Hash"
                      },
                      "existing_video_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing Meta video ID (for video ad_type)",
                        "title": "Existing Video Id"
                      },
                      "facebook_page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook Page ID",
                        "title": "Facebook Page Id"
                      },
                      "facebook_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook placements: feed, right_hand_column, story, facebook_reels, marketplace, search, etc. Only listed positions are included.",
                        "title": "Facebook Positions"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "title": "Genders"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Headline (max 255 chars, recommended 40)",
                        "title": "Headline"
                      },
                      "image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Public URL of image to use (for image ad_type)",
                        "title": "Image Url"
                      },
                      "instagram_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram account ID",
                        "title": "Instagram Account Id"
                      },
                      "instagram_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram placements: stream (feed), story, reels, explore, explore_home, ig_search, profile_feed. Only listed positions are included.",
                        "title": "Instagram Positions"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "title": "Interests"
                      },
                      "is_dynamic_creative": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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/primary_texts fields.",
                        "title": "Is Dynamic Creative"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL (HTTPS)",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "lead_form_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lead form ID for OUTCOME_LEADS campaigns. If not provided, auto-fetched from existing campaign ads.",
                        "title": "Lead Form Id"
                      },
                      "locations": {
                        "anyOf": [
                          {
                            "items": {},
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Location targeting. Country codes (['US']) or location objects from search_meta_targeting ([{'key': '2421836', 'type': 'city', 'radius': 25, 'distance_unit': 'mile'}]). Default: ['US']",
                        "title": "Locations"
                      },
                      "multi_advertiser": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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).",
                        "title": "Multi Advertiser"
                      },
                      "multi_share_optimized": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": true,
                        "description": "For carousel: optimize card order",
                        "title": "Multi Share Optimized"
                      },
                      "pixel_event_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Conversion event: PURCHASE, LEAD, ADD_TO_CART, OTHER, etc. Use OTHER with custom_conversion_id for custom events.",
                        "title": "Pixel Event Name"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Pixel ID for conversion tracking",
                        "title": "Pixel Id"
                      },
                      "primary_text": {
                        "description": "Primary ad text (max 2200 chars, recommended 125). Supports emojis and line breaks.",
                        "maxLength": 2200,
                        "title": "Primary Text",
                        "type": "string"
                      },
                      "publisher_platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Which platforms to run on: ['facebook', 'instagram', 'audience_network', 'messenger']. Only listed platforms are included. Default: automatic (all).",
                        "title": "Publisher Platforms"
                      },
                      "thumbnail_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom thumbnail URL for video",
                        "title": "Thumbnail Url"
                      },
                      "video_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Public URL of video to upload (for video ad_type)",
                        "title": "Video Url"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "ad_type",
                      "primary_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_meta_ad_set)"
                  },
                  "success": true,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_meta_ad_set",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to add a new ad set to an EXISTING campaign [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_meta_ad_set"
      }
    },
    "/api/v1/tools/add_negative_keywords/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd negative keywords to a campaign.\n\nNegative keywords prevent your ads from showing for certain searches.\nThey help save budget by avoiding irrelevant clicks.\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)\n- keywords: List of negative keywords to add\n- customer_id: Optional\n\n**Each keyword needs:**\n- text: The keyword phrase (e.g., \"free\", \"cheap\", \"diy\")\n- match_type: EXACT, PHRASE, or BROAD (default: BROAD)\n\n**Campaign-level vs Ad Group-level:**\nThis adds negative keywords at CAMPAIGN level, affecting ALL ad groups.\n\n**Common negative keywords:**\n- \"free\", \"cheap\", \"discount\" (for premium brands)\n- \"jobs\", \"salary\", \"careers\" (for product companies)\n- \"diy\", \"how to\", \"tutorial\" (for service providers)\n\n**Execution time:** 2-4 seconds\n\n**Example:**\nUser: \"Block searches containing 'free' and 'cheap'\"\nAgent: Uses add_negative_keywords with campaign_id and keywords list",
        "operationId": "execute_add_negative_keywords",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "keywords": [
                    {
                      "match_type": "BROAD",
                      "text": "free"
                    },
                    {
                      "match_type": "PHRASE",
                      "text": "cheap"
                    }
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding negative keywords to a campaign",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to add negative keywords to. Use list_campaigns first.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "keywords": {
                        "description": "List of negative keywords. Each: {text, match_type (EXACT/PHRASE/BROAD)}. These prevent ads from showing for unwanted searches.",
                        "items": {
                          "additionalProperties": true,
                          "type": "object"
                        },
                        "title": "Keywords",
                        "type": "array"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "keywords"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_negative_keywords)"
                  },
                  "success": true,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_negative_keywords",
                  "is_error": true,
                  "success": false,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_negative_keywords"
      }
    },
    "/api/v1/tools/add_pmax_audience_signal/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd an audience signal to an existing Performance Max campaign.\n\nAudience signals hint Google AI about who your ideal customers are.\nUses in-market segments (actively researching), affinity segments (long-term interests),\nand custom audiences (remarketing lists, customer match).\n\nUse search_audiences tool first to discover relevant audience segment IDs.\n\n**Parameters:**\n- campaign_id: The PMax campaign ID\n- audience_config: Dict with segment IDs\n\n**Execution time:** 2-5 seconds",
        "operationId": "execute_add_pmax_audience_signal",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "audience_config": {
                    "affinity_audience_ids": [
                      10003100
                    ],
                    "custom_audience_ids": [
                      "customers/1234567890/customAudiences/555"
                    ],
                    "in_market_audience_ids": [
                      90013100
                    ],
                    "user_list_ids": [
                      "customers/1234567890/userLists/999"
                    ]
                  },
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding an audience signal to a PMax campaign.",
                    "properties": {
                      "audience_config": {
                        "additionalProperties": true,
                        "description": "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' (List[int]), 'custom_audience_ids' (List[str] customAudiences resource names), 'user_list_ids' (List[str] userLists resource names from search_audiences custom results), 'audience_name' (str). Example: {'in_market_audience_ids': [80432, 80210], 'user_list_ids': ['customers/123/userLists/456'], 'audience_name': 'SaaS Buyers'}",
                        "title": "Audience Config",
                        "type": "object"
                      },
                      "campaign_id": {
                        "description": "The Google Ads campaign ID (numeric). Example: '21854471508'",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "audience_config"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_pmax_audience_signal)"
                  },
                  "success": true,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_pmax_audience_signal",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_pmax_audience_signal"
      }
    },
    "/api/v1/tools/add_pmax_search_themes/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd search themes to an existing Performance Max campaign.\n\nSearch themes hint Google AI about what customers search for. Max 50 per asset group.\nNOT keywords \u2014 no match types, no bids, no quality scores.\n\n**Best practices:**\n- Derive from keyword research results, business profile, or user input\n- Keep themes specific but not too narrow (2-5 words ideal)\n- Avoid generic themes like \"buy stuff\" or overly specific ones like \"buy red nike air max 97 size 12\"\n\n**Parameters:**\n- campaign_id: The PMax campaign ID\n- search_themes: List of theme strings to add\n\n**Execution time:** 2-5 seconds",
        "operationId": "execute_add_pmax_search_themes",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "search_themes": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding search themes to a PMax campaign.",
                    "properties": {
                      "campaign_id": {
                        "description": "The Google Ads campaign ID (numeric). Example: '21854471508'",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "search_themes": {
                        "description": "List of search themes to add. Max 50 total per asset group. NOT keywords \u2014 these are hints for Google AI about what customers search. Example: ['AI ad management', 'automate google ads']",
                        "items": {
                          "type": "string"
                        },
                        "minItems": 1,
                        "title": "Search Themes",
                        "type": "array"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "search_themes"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_pmax_search_themes)"
                  },
                  "success": true,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_pmax_search_themes",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_pmax_search_themes"
      }
    },
    "/api/v1/tools/add_sitelinks/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd sitelink extensions to a campaign.\n\nSitelinks are clickable links to specific pages on your website.\nRecommended 4-6 sitelinks per campaign.\n\n**CONSTRAINTS:**\n- link_text max 25 characters\n- description1 max 35 characters (optional)\n- description2 max 35 characters (required if description1 is set)\n\n**EXAMPLE SITELINKS:**\n{\n    \"link_text\": \"How It Works\",\n    \"final_url\": \"https://example.com/how-it-works\",\n    \"description1\": \"See the platform in action\",\n    \"description2\": \"Step-by-step walkthrough\"\n}\n\n{\n    \"link_text\": \"Pricing\",\n    \"final_url\": \"https://example.com/pricing\",\n    \"description1\": \"Affordable plans for all\",\n    \"description2\": \"Start free today\"\n}\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.\n- sitelinks: List of sitelink objects with link_text, final_url, and optional descriptions.\n- customer_id: Optional Google Ads customer ID\n\n**Execution time:** 2-5 seconds\n\n**When to use:**\n- User wants to add sitelinks to their campaign\n- User asks about adding more links to ads\n- After creating a campaign, suggest adding sitelinks\n\n**Example:**\nUser: \"Add sitelinks to my campaign\"\nAgent:\n1. Uses list_campaigns to get campaign_id\n2. Uses add_sitelinks with relevant page links",
        "operationId": "execute_add_sitelinks",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "sitelinks": [
                    {
                      "final_url": "https://example.com/running",
                      "link_text": "Shop Running"
                    },
                    {
                      "final_url": "https://example.com/new",
                      "link_text": "New Arrivals"
                    }
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding sitelink extensions to a campaign.\n\nSitelinks are clickable links to specific pages on your website.\nRecommended 4-6 sitelinks per campaign.\n\nEXAMPLE:\n    {\n        \"link_text\": \"How It Works\",\n        \"final_url\": \"https://example.com/how-it-works\",\n        \"description1\": \"See the platform in action\",\n        \"description2\": \"Step-by-step walkthrough\"\n    }\n\nConstraints:\n- link_text max 25 characters\n- description1 max 35 characters (optional)\n- description2 max 35 characters (required if description1 set)",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to add sitelinks to. Get from list_campaigns.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "sitelinks": {
                        "description": "List of sitelink objects. Each has 'link_text' (max 25 chars), 'final_url', and optional 'description1'/'description2' (max 35 chars each).",
                        "items": {
                          "additionalProperties": true,
                          "type": "object"
                        },
                        "title": "Sitelinks",
                        "type": "array"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "sitelinks"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_sitelinks)"
                  },
                  "success": true,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_sitelinks",
                  "is_error": true,
                  "success": false,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_sitelinks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_sitelinks"
      }
    },
    "/api/v1/tools/add_structured_snippets/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAdd structured snippet extensions to a campaign.\n\nStructured snippets highlight specific aspects of your products/services.\n\n**VALID HEADERS:**\n- AMENITIES, BRANDS, COURSES, DEGREE_PROGRAMS, DESTINATIONS\n- FEATURED_HOTELS, INSURANCE_COVERAGE, MODELS, NEIGHBORHOODS\n- SERVICE_CATALOG, SHOWS, STYLES, TYPES\n\n**RULES:**\n- Header must be from predefined list\n- 3-10 values required per snippet\n- Each value max 25 characters\n\n**EXAMPLES:**\nFor SaaS/Platform:\n    Header: \"Types\"\n    Values: [\"Google Ads\", \"Meta Ads\", \"LinkedIn Ads\", \"TikTok Ads\"]\n\nFor Services:\n    Header: \"Services\" (maps to SERVICE_CATALOG)\n    Values: [\"Campaign Launch\", \"Analytics\", \"Optimization\", \"Reporting\"]\n\nFor Brands:\n    Header: \"Brands\"\n    Values: [\"Nike\", \"Adidas\", \"Puma\", \"New Balance\"]\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.\n- snippets: List of {header, values} objects (REQUIRED).\n- customer_id: Optional Google Ads customer ID\n\n**Execution time:** 2-5 seconds\n\n**When to use:**\n- User wants to add structured snippets\n- User asks to highlight product types, services, or brands\n- After creating a campaign, suggest adding snippets\n\n**Example:**\nUser: \"Add structured snippets showing our ad platform types\"\nAgent:\n1. Uses list_campaigns to get campaign_id\n2. Uses add_structured_snippets with header \"Types\" and relevant values",
        "operationId": "execute_add_structured_snippets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "snippets": [
                    {
                      "header": "Brands",
                      "values": [
                        "Nike",
                        "Adidas",
                        "Puma"
                      ]
                    }
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for adding structured snippet extensions to a campaign.\n\nStructured snippets highlight specific aspects of your products/services.\n\nValid Headers: AMENITIES, BRANDS, COURSES, DEGREE_PROGRAMS, DESTINATIONS,\nFEATURED_HOTELS, INSURANCE_COVERAGE, MODELS, NEIGHBORHOODS, SERVICE_CATALOG,\nSHOWS, STYLES, TYPES\n\nEXAMPLE for SaaS:\n    Header: \"Types\"\n    Values: [\"Google Ads\", \"Meta Ads\", \"LinkedIn Ads\", \"TikTok Ads\"]\n\nEXAMPLE for Services:\n    Header: \"Services\" (maps to SERVICE_CATALOG)\n    Values: [\"Campaign Launch\", \"Analytics\", \"Optimization\", \"Reporting\"]",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to add snippets to. Get from list_campaigns.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "snippets": {
                        "description": "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']}]",
                        "items": {
                          "additionalProperties": true,
                          "type": "object"
                        },
                        "title": "Snippets",
                        "type": "array"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "snippets"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_structured_snippets)"
                  },
                  "success": true,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_structured_snippets",
                  "is_error": true,
                  "success": false,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_structured_snippets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_structured_snippets"
      }
    },
    "/api/v1/tools/add_tiktok_ad/execute": {
      "post": {
        "description": "Add a new ad to an existing TikTok ad group.\n\nUse this when you need to add additional ads (creatives) to an ad group that was already created.\n\nSupports all ad formats:\n- SINGLE_IMAGE: Provide image_urls (public HTTPS URLs \u2014 auto-uploaded) OR image_ids (pre-uploaded TikTok IDs)\n- SINGLE_VIDEO: Provide video_id (TikTok video ID from creative library)\n- Spark Ads: Boost organic TikTok posts (requires tiktok_item_id + identity_type=TT_USER)\n- Carousel: Multi-card ads (first call create_tiktok_carousel_card to get card_id, then provide card_id + card_type here)\n\nIdentity (identity_id) is auto-resolved \u2014 you do NOT need to provide it unless using Spark Ads.\nImages are auto-uploaded from URLs \u2014 you do NOT need to call upload_tiktok_images first.\n\nThis is useful for:\n- Testing different creatives in the same ad group\n- Adding new ad variations without creating a new campaign\n- A/B testing ad copy, images, or CTAs\n\nRequired: adgroup_id, ad_text, landing_page_url.\nFor image ads: provide image_urls (easiest) or image_ids.\nFor video ads: provide video_id.\nFor Spark Ads: provide tiktok_item_id and set identity_type to TT_USER.",
        "operationId": "execute_add_tiktok_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_format": "SINGLE_IMAGE",
                  "ad_name": "string",
                  "ad_text": "string",
                  "adgroup_id": "string",
                  "call_to_action": "string",
                  "display_name": "string",
                  "image_ids": [
                    "string"
                  ],
                  "image_urls": [
                    "string"
                  ],
                  "landing_page_url": "https://example.com"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for adding a new ad to an existing TikTok ad group",
                    "properties": {
                      "ad_format": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "SINGLE_IMAGE",
                        "description": "Ad format: SINGLE_IMAGE, SINGLE_VIDEO. Default: SINGLE_IMAGE.",
                        "title": "Ad Format"
                      },
                      "ad_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Ad name. Auto-generated if not provided.",
                        "title": "Ad Name"
                      },
                      "ad_text": {
                        "description": "Ad text (1-100 characters).",
                        "maxLength": 100,
                        "minLength": 1,
                        "title": "Ad Text",
                        "type": "string"
                      },
                      "adgroup_id": {
                        "description": "Existing TikTok ad group ID to add the ad to",
                        "title": "Adgroup Id",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID (optional).",
                        "title": "Advertiser Id"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "CTA button: LEARN_MORE, SHOP_NOW, SIGN_UP, DOWNLOAD, CONTACT_US, APPLY_NOW, etc.",
                        "title": "Call To Action"
                      },
                      "card_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel card ID.",
                        "title": "Card Id"
                      },
                      "card_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Card type: IMAGE or PRODUCT.",
                        "title": "Card Type"
                      },
                      "display_name": {
                        "anyOf": [
                          {
                            "maxLength": 40,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Brand name (max 40 chars).",
                        "title": "Display Name"
                      },
                      "identity_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Advertiser identity ID. Auto-resolved if not provided.",
                        "title": "Identity Id"
                      },
                      "identity_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Identity type: CUSTOMIZED_USER, BC_AUTH_TT, TT_USER. Auto-resolved if not provided.",
                        "title": "Identity Type"
                      },
                      "image_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok image IDs from upload_tiktok_images (for SINGLE_IMAGE).",
                        "title": "Image Ids"
                      },
                      "image_urls": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Public image URLs (HTTPS) \u2014 auto-uploaded to TikTok. Use this instead of image_ids for convenience.",
                        "title": "Image Urls"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL (must be HTTPS).",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "tiktok_item_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok organic post ID for Spark Ads (boost existing TikTok posts as ads).",
                        "title": "Tiktok Item Id"
                      },
                      "video_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok video ID (for SINGLE_VIDEO format).",
                        "title": "Video Id"
                      }
                    },
                    "required": [
                      "adgroup_id",
                      "ad_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_tiktok_ad)"
                  },
                  "success": true,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_tiktok_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Add a new ad to an existing TikTok ad group [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_tiktok_ad"
      }
    },
    "/api/v1/tools/add_tiktok_ad_group/execute": {
      "post": {
        "description": "Add a new ad group to an existing TikTok campaign.\n\nUse this when you need to add additional ad groups to a campaign that was already created.\nEach ad group can have different targeting, budget, and placement settings.\n\nThis is useful for:\n- A/B testing different audiences under the same campaign\n- Splitting budget across different targeting strategies\n- Adding new targeting segments to an existing campaign\n\nRequired: campaign_id, adgroup_name, budget.\nThe objective parameter determines automatic billing/optimization defaults (TRAFFIC\u2192CPC, CONVERSIONS\u2192OCPM, etc.).\n\nAfter creating the ad group, use `add_tiktok_ad` to add ads to it.",
        "operationId": "execute_add_tiktok_ad_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "adgroup_name": "string",
                  "age_groups": [
                    "string"
                  ],
                  "budget": 1.0,
                  "budget_mode": "BUDGET_MODE_DAY",
                  "campaign_id": "<campaign_id>",
                  "gender": "string",
                  "location_ids": [
                    "string"
                  ],
                  "objective": "TRAFFIC",
                  "schedule_start_time": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for adding a new ad group to an existing TikTok campaign",
                    "properties": {
                      "adgroup_name": {
                        "description": "Name for the new ad group",
                        "title": "Adgroup Name",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID (optional).",
                        "title": "Advertiser Id"
                      },
                      "age_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Age groups: AGE_13_17, AGE_18_24, AGE_25_34, AGE_35_44, AGE_45_54, AGE_55_100.",
                        "title": "Age Groups"
                      },
                      "audience_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom audience IDs to include.",
                        "title": "Audience Ids"
                      },
                      "billing_event": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override billing event: CPC, CPM, OCPM, CPV.",
                        "title": "Billing Event"
                      },
                      "budget": {
                        "description": "Ad group budget in account currency (min varies by region)",
                        "minimum": 1.0,
                        "title": "Budget",
                        "type": "number"
                      },
                      "budget_mode": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "BUDGET_MODE_DAY",
                        "description": "BUDGET_MODE_DAY (daily) or BUDGET_MODE_TOTAL (lifetime). Default: daily.",
                        "title": "Budget Mode"
                      },
                      "campaign_id": {
                        "description": "Existing TikTok campaign ID to add the ad group to",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "comment_disabled": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Disable comments on ads.",
                        "title": "Comment Disabled"
                      },
                      "excluded_audience_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom audience IDs to exclude.",
                        "title": "Excluded Audience Ids"
                      },
                      "gender": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "GENDER_UNLIMITED, GENDER_MALE, GENDER_FEMALE.",
                        "title": "Gender"
                      },
                      "interest_category_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Interest category IDs for targeting.",
                        "title": "Interest Category Ids"
                      },
                      "languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Language codes (e.g., ['en', 'es']).",
                        "title": "Languages"
                      },
                      "location_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok location IDs. Default: ['6252001'] (US).",
                        "title": "Location Ids"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "TRAFFIC",
                        "description": "Campaign objective (determines billing/optimization defaults). Options: TRAFFIC, CONVERSIONS, LEAD_GENERATION, REACH, VIDEO_VIEWS, APP_PROMOTION. Default: TRAFFIC.",
                        "title": "Objective"
                      },
                      "operating_systems": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target OS: ANDROID, IOS.",
                        "title": "Operating Systems"
                      },
                      "optimization_event": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Optimization Event"
                      },
                      "optimization_goal": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override optimization goal: CLICK, CONVERT, REACH, etc.",
                        "title": "Optimization Goal"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok Pixel ID for conversion tracking.",
                        "title": "Pixel Id"
                      },
                      "placement_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "PLACEMENT_TYPE_AUTOMATIC or PLACEMENT_TYPE_NORMAL.",
                        "title": "Placement Type"
                      },
                      "placements": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Manual placements: PLACEMENT_TIKTOK, PLACEMENT_PANGLE, PLACEMENT_GLOBAL_APP_BUNDLE.",
                        "title": "Placements"
                      },
                      "promotion_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override promotion type: WEBSITE, APP_ANDROID, APP_IOS.",
                        "title": "Promotion Type"
                      },
                      "schedule_end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End time: YYYY-MM-DD HH:MM:SS. Required for BUDGET_MODE_TOTAL.",
                        "title": "Schedule End Time"
                      },
                      "schedule_start_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start time: YYYY-MM-DD HH:MM:SS.",
                        "title": "Schedule Start Time"
                      },
                      "video_download_disabled": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Disable video download.",
                        "title": "Video Download Disabled"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "adgroup_name",
                      "budget"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for add_tiktok_ad_group)"
                  },
                  "success": true,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: add_tiktok_ad_group",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "add_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Add a new ad group to an existing TikTok campaign [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "add_tiktok_ad_group"
      }
    },
    "/api/v1/tools/analyze_linkedin_creative_performance/execute": {
      "post": {
        "description": "User asks about ad/creative performance,\nwants to identify winning/losing ad variations, or asks about creative fatigue.\n\nAnalyzes LinkedIn creative/ad performance:\n\nReturns:\n- Top performing creatives by engagement rate\n- Underperforming creatives needing attention\n- Creative fatigue indicators (running too long)\n- Video performance (views, completion rates)\n- Engagement breakdown per creative\n- Recommendations for creative refresh\n\nCreative Metrics:\n- Impressions, clicks, CTR\n- Engagement rate (likes, comments, shares)\n- Lead generation (forms, completions)\n- Video metrics (views, quartile completions)\n- Days running (for fatigue detection)\n\nParameters:\n- lookback_days: Number of days to analyze (7-120). Default: 30\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- campaign_id: Optional filter to specific campaign\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"Which LinkedIn ads are performing best?\"\n- \"Show me creative performance on LinkedIn\"\n- \"Are any LinkedIn creatives fatigued?\"\n- \"Which ads should I refresh on LinkedIn?\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_analyze_linkedin_creative_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "string",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for analyzing LinkedIn creative/ad performance",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Filter to specific campaign",
                        "title": "Campaign Id"
                      },
                      "campaign_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by campaign name (partial match).",
                        "title": "Campaign Name"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7-120). Default: 30.",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_linkedin_creative_performance)"
                  },
                  "success": true,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_linkedin_creative_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_creative_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about ad/creative performance,\nwants to identify winning/losing ad variations, or asks about creative fatigue",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_linkedin_creative_performance"
      }
    },
    "/api/v1/tools/analyze_linkedin_wasted_spend/execute": {
      "post": {
        "description": "User asks about wasted ad spend, unprofitable campaigns,\nwhere their LinkedIn budget is being wasted, or wants to identify underperformers.\n\nIdentifies LinkedIn campaigns and B2B segments wasting money:\n\nCampaign Analysis:\n- Campaigns losing money (ROAS < 1.0 = actual loss)\n- Campaigns underperforming (ROAS below target = opportunity cost)\n- Severity classification (CRITICAL, HIGH, MEDIUM)\n- Wasted spend calculation per campaign\n\nB2B Demographic Waste:\n- Seniority levels with poor ROAS\n- Industries not converting\n- Company sizes wasting budget\n- Job functions underperforming\n\nCreative Waste:\n- Fatigued creatives (14-day threshold)\n- Low engagement creatives\n- High spend but low lead creatives\n\nReturns:\n- Total wasted spend and percentage\n- Campaigns to pause/reduce\n- Segments to exclude\n- Quick actions with monthly savings estimates\n\nParameters:\n- lookback_days: Number of days to analyze (7-120). Default: 30\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- target_roas: Override target ROAS threshold\n- include_demographic_breakdown: Include B2B segment waste. Default: true\n- include_creative_analysis: Include creative-level waste. Default: true\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"Where am I wasting money on LinkedIn?\"\n- \"Which LinkedIn campaigns are losing money?\"\n- \"Show me LinkedIn wasted spend\"\n- \"Which B2B segments should I stop targeting?\"\n- \"How can I reduce LinkedIn ad waste?\"\n\nExecution time: 4-6 seconds\n\n**Quick Actions (IMPORTANT \u2014 read severity context first):**\n- \u23f3 LEARNING campaigns \u2192 Do NOT pause. Monitor for 14+ days before judging.\n- \u2753 INSUFFICIENT_DATA campaigns \u2192 Need more spend before analysis is meaningful.\n- \ud83d\udea8 CRITICAL campaigns (established, 14+ days, ZERO conversions) \u2192 Consider pausing\n- \ud83d\udea8 CRITICAL campaigns (established, 14+ days, HAS conversions) \u2192 Review performance, verify revenue in ad platform before reducing budget\n- \ud83d\udd34 HIGH severity (established, 14+ days) \u2192 Consider reducing budget by 50-70%\n- \ud83d\udfe1 MEDIUM \u2192 Optimize targeting, ad copy, landing pages\n\n\u26a0\ufe0f **NEVER say \"pause\" for a campaign that has conversions.** Say \"review\" or \"reduce budget\" instead.\n\u26a0\ufe0f **NEVER recommend pausing a campaign in LEARNING phase.**\n\u26a0\ufe0f **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.**\n\u26a0\ufe0f **Consider campaign objective: brand awareness campaigns will not have ROAS data. This is normal.**\n\u26a0\ufe0f **When data confidence is MEDIUM or LOW, soften all recommendations and add verification prompts.**",
        "operationId": "execute_analyze_linkedin_wasted_spend",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string",
                  "target_roas": 0.1
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for analyzing LinkedIn wasted spend",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "include_creative_analysis": {
                        "default": true,
                        "description": "Include creative-level waste analysis (fatigue, low engagement). Default: true",
                        "title": "Include Creative Analysis",
                        "type": "boolean"
                      },
                      "include_demographic_breakdown": {
                        "default": true,
                        "description": "Include B2B demographic waste analysis (seniority, industry, company size). Default: true",
                        "title": "Include Demographic Breakdown",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7-120). Default: 30.",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "minimum": 0.1,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override target ROAS threshold. Campaigns below this ROAS are flagged as underperforming.",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_linkedin_wasted_spend)"
                  },
                  "success": true,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_linkedin_wasted_spend",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_linkedin_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about wasted ad spend, unprofitable campaigns,\nwhere their LinkedIn budget is being wasted, or wants to ide...",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_linkedin_wasted_spend"
      }
    },
    "/api/v1/tools/analyze_meta_ad_performance/execute": {
      "post": {
        "description": "User wants detailed analysis of specific Meta ads, creative performance, or wants to identify winning/losing ad variations.\n\nThis tool provides ad-level and creative-level insights for Meta campaigns.\n\nReturns:\n- Top performing ads by CTR and conversion rate\n- Underperforming ads that need attention\n- Creative fatigue indicators (high frequency, declining CTR)\n- Video completion metrics (25%, 50%, 75%, 100% watched)\n- Creative optimization recommendations\n\nWhen to use this tool:\n- \"Which of my Meta ads are performing best?\"\n- \"Are any of my Facebook ads fatigued?\"\n- \"Show me my Instagram video ad performance\"\n- \"Which creatives should I pause?\"\n- \"Analyze my ad variations\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- campaign_id: Optional - filter to specific campaign\n- include_video_metrics: true (default) or false\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 1-3 seconds (cached database query)\nData source: ad_group_daily_metrics table (Meta ad sets stored as ad groups)\n\nCreative fatigue detection:\n- High frequency (>4.0) with declining CTR\n- CTR dropped >20% from first week\n- Same creative running >14 days without refresh",
        "operationId": "execute_analyze_meta_ad_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "string",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta ad/creative performance analysis",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter to specific campaign ID (optional - analyzes all campaigns if not provided)",
                        "title": "Campaign Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "include_video_metrics": {
                        "default": true,
                        "description": "Include video completion metrics (25%, 50%, 75%, 100% watched). Default is true.",
                        "title": "Include Video Metrics",
                        "type": "boolean"
                      },
                      "limit": {
                        "default": 100,
                        "description": "Maximum number of ads to return (default 100, max 200). Use with offset for pagination.",
                        "title": "Limit",
                        "type": "integer"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "offset": {
                        "default": 0,
                        "description": "Number of ads to skip for pagination (default 0). Use with limit to get next page.",
                        "title": "Offset",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_meta_ad_performance)"
                  },
                  "success": true,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_meta_ad_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants detailed analysis of specific Meta ads, creative performance, or wants to identify winning/losing ad varia...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_meta_ad_performance"
      }
    },
    "/api/v1/tools/analyze_meta_audiences/execute": {
      "post": {
        "description": "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.\n\nThis tool provides deep analysis of audience segment performance and detects audience saturation to optimize demographic targeting for Meta Ads.\n\nReturns:\n- Age group performance breakdown (18-24, 25-34, 35-44, 45-54, 55-64, 65+)\n- Gender performance breakdown (male, female, unknown)\n- Age + Gender combination analysis\n- Segments categorized as SCALE/MAINTAIN/REDUCE/EXCLUDE based on ROAS\n- Audience saturation score (0-100) with contributing factors\n- Best performing segments to scale\n- Underperforming segments to reduce/exclude\n- Targeting optimization recommendations\n- Quick actionable items\n\nWhen to use this tool:\n- \"Which age groups perform best for my Meta ads?\"\n- \"Should I target men or women on Facebook?\"\n- \"Is my Meta audience saturated?\"\n- \"Which demographics should I exclude?\"\n- \"Analyze my Instagram audience performance\"\n- \"Best demographic targeting for my Facebook campaigns\"\n- \"Age and gender breakdown for my Meta ads\"\n- \"Are my lookalike audiences exhausted?\"\n- \"Which audience segments are wasting money?\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- breakdown_type: 'age', 'gender', 'age_gender', or 'all' (default)\n- include_saturation: Include saturation analysis (default: True)\n- target_roas: Optional override (default: from account goals or 2.0x)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 2-5 seconds (cached database query with analysis)\nData source: meta_audience_daily_metrics table (demographic-level daily metrics)\n\nROAS Thresholds for Segment Recommendations:\n- \ud83d\ude80 SCALE (ROAS \u2265 1.5x target): Increase budget to this segment\n- \u2796 MAINTAIN (ROAS 0.75x-1.5x target): Keep current allocation\n- \u26a0\ufe0f REDUCE (ROAS 0.5x-0.75x target): Decrease budget\n- \ud83d\udd34 EXCLUDE (ROAS < 0.5x target): Remove from targeting\n\nSaturation Score Factors (weighted):\n- Frequency Score (35%): How often users see ads (>3 indicates fatigue)\n- CTR Decline Score (30%): Week-over-week CTR changes\n- CPA Increase Score (25%): Rising cost per acquisition\n- Reach Saturation Score (10%): Audience reach exhaustion\n\nSaturation Levels:\n- \u2705 HEALTHY (<40): Audience is fresh, continue scaling\n- \ud83d\udfe1 AT_RISK (40-69): Monitor frequency, prepare new audiences\n- \ud83d\udd34 SATURATED (\u226570): Expand targeting or refresh creatives\n\nCommon Insights:\n- Age 25-44 typically has highest ROAS for e-commerce\n- Gender targeting varies significantly by product category\n- High frequency (>4) combined with declining CTR indicates saturation\n- Lookalike audiences can exhaust within 4-8 weeks at high spend",
        "operationId": "execute_analyze_meta_audiences",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "breakdown_type": "all",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta audience performance analysis",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "breakdown_type": {
                        "default": "all",
                        "description": "Type of audience breakdown: 'age' (age groups), 'gender', 'age_gender' (combined), or 'all' (default).",
                        "title": "Breakdown Type",
                        "type": "string"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "include_saturation": {
                        "default": true,
                        "description": "Include audience saturation analysis for lookalike and custom audiences (default: True).",
                        "title": "Include Saturation",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average.",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_meta_audiences)"
                  },
                  "success": true,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_meta_audiences",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about Meta/Facebook/Instagram audience performance by demographics, age group or gender targeting optimizat...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_meta_audiences"
      }
    },
    "/api/v1/tools/analyze_meta_wasted_spend/execute": {
      "post": {
        "description": "User asks about Meta/Facebook/Instagram ad spend efficiency, wasted money, underperforming campaigns, placement optimization, or creative fatigue.\n\nThis 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:\n\nReturns:\n- Campaign-level wasted spend analysis (ROAS < 1.0 = actual losses)\n- Underperforming spend analysis (1.0 <= ROAS < target = opportunity cost)\n- Placement-level breakdown (which placements are wasting money: Feed, Stories, Reels, etc.)\n- Creative fatigue detection (high frequency + declining CTR)\n- Actionable recommendations for campaigns, placements, and creatives\n- Quick action items\n\nWhen to use this tool:\n- \"Where am I wasting money on Meta/Facebook ads?\"\n- \"Which Meta placements should I exclude?\"\n- \"Are any of my Facebook creatives fatigued?\"\n- \"How can I reduce wasted ad spend on Instagram?\"\n- \"Which Meta campaigns are losing money?\"\n- \"What's my Meta ROAS by placement?\"\n\nParameters:\n- lookback_days: 7, 30 (default), 60, 90, or 120 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- target_roas: Optional override (e.g., 3.0 for 3.0x ROAS)\n- include_placements: true (default) - Include placement-level analysis\n- include_fatigue: true (default) - Include creative fatigue analysis\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 2-5 seconds (cached database query with analysis)\nData source: campaign_daily_metrics + meta_placement_daily_metrics + meta_ad_creative_metrics tables\n\nKey definitions:\n- Wasted Spend (ROAS < 1.0): Actual money lost - for every $1 spent, getting back <$1\n- Underperforming (1.0 <= ROAS < target): Profitable but below target - opportunity cost\n- Creative Fatigue: Ads with frequency >4x (cold traffic) or >7x (retargeting) showing declining CTR\n\nMeta-specific placement optimization:\n- Audience Network often has lowest ROAS - consider excluding\n- Instagram Stories/Reels typically perform differently than Feed\n- Facebook Marketplace can waste spend if not relevant\n\n**Quick Actions (IMPORTANT \u2014 read severity context first):**\n- \u23f3 LEARNING campaigns \u2192 Do NOT pause. Monitor for 14+ days before judging.\n- \u2753 INSUFFICIENT_DATA campaigns \u2192 Need more spend before analysis is meaningful.\n- \ud83d\udea8 CRITICAL campaigns (established, 14+ days, ZERO conversions) \u2192 Consider pausing\n- \ud83d\udea8 CRITICAL campaigns (established, 14+ days, HAS conversions) \u2192 Review performance, verify revenue in ad platform before reducing budget\n- \ud83d\udd34 HIGH severity (established, 14+ days) \u2192 Consider reducing budget by 50-70%\n- \ud83d\udfe1 MEDIUM \u2192 Optimize targeting, ad copy, landing pages\n\n\u26a0\ufe0f **NEVER say \"pause\" for a campaign that has conversions.** Say \"review\" or \"reduce budget\" instead.\n\u26a0\ufe0f **NEVER recommend pausing a campaign in LEARNING phase.**\n\u26a0\ufe0f **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.**\n\u26a0\ufe0f **Consider campaign objective: brand awareness campaigns will not have ROAS data. This is normal.**\n\u26a0\ufe0f **When data confidence is MEDIUM or LOW, soften all recommendations and add verification prompts.**",
        "operationId": "execute_analyze_meta_wasted_spend",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string",
                  "target_roas": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta wasted spend analysis with placement and fatigue detection",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "include_fatigue": {
                        "default": true,
                        "description": "Include creative fatigue analysis. Default is true.",
                        "title": "Include Fatigue",
                        "type": "boolean"
                      },
                      "include_placements": {
                        "default": true,
                        "description": "Include placement-level analysis (Feed, Stories, Reels, etc.). Default is true.",
                        "title": "Include Placements",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 30, 60, 90, or 120 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average.",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_meta_wasted_spend)"
                  },
                  "success": true,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_meta_wasted_spend",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_meta_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about Meta/Facebook/Instagram ad spend efficiency, wasted money, underperforming campaigns, placement optim...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_meta_wasted_spend"
      }
    },
    "/api/v1/tools/analyze_search_terms/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nDiscover keyword opportunities and optimize match types by analyzing actual search terms.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times. Google Ads only - NOT available for TikTok.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1 Feature 3):**\n- Identifies OPPORTUNITY keywords: Converting search terms NOT in your keyword list\n- Identifies NEGATIVE keywords: Expensive terms with zero conversions\n- Analyzes MATCH TYPE performance: Exact vs Phrase vs Broad efficiency\n- Provides specific recommendations with suggested bids and match types\n- Ranks opportunities by profit potential (not just ROAS)\n\n**Returns comprehensive keyword intelligence:**\n- Top 20 opportunity keywords with suggested bids and match types\n- Top 20 negative keyword candidates with wasted cost breakdown\n- Match type performance comparison (Exact/Phrase/Broad)\n- Broad\u2192Exact conversion recommendations\n- Total opportunity value and wasted spend amounts\n- Actionable recommendations for immediate implementation\n\n\ud83d\udd0d **What Search Term Mining Reveals:**\n\n**Opportunity Keywords (Money Left on Table):**\n- Search terms that ARE converting but you're NOT bidding on them directly\n- These are queries triggering your ads via Broad/Phrase match, but should be added as Exact keywords\n- Example: User searches \"enterprise security software pricing\" \u2192 converts \u2192 but you only have \"security software\" as keyword\n- **Action:** Add high-converting search terms as new keywords with suggested bids\n\n**Negative Keywords (Money Wasted):**\n- Search terms costing >$10 with ZERO conversions\n- Low-intent queries like \"free\", \"download\", \"crack\", \"how to\"\n- Example: \"free security software download\" \u2192 120 clicks, $540 spent, 0 conversions\n- **Action:** Add as negative keywords to stop wasting budget\n\n**Match Type Optimization:**\n- Compares performance of Exact vs Phrase vs Broad match\n- Identifies Broad keywords that should be converted to Exact for better control\n- Example: Broad \"security software\" has 45% wasted spend, but top search terms have 20x ROAS\n- **Action:** Convert high-performing Broad keywords to Exact match\n\n**Parameters:**\n- lookback_days: 7, 30, 60, 90, or 120 days (default: 30)\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- analysis_type: 'opportunities', 'negatives', 'match_types', 'all', or **'raw_report'** (default: 'all')\n  - Use **'raw_report'** when the user wants to SEE their actual search terms \u2014 the words customers type into Google\n- force_refresh: true to trigger immediate API collection (default: false, uses cached data)\n- customer_id: Optional (uses connected account if omitted)\n- **campaign_id**: Optional \u2014 filter search terms to a specific campaign (get from list_campaigns)\n- **page**: Page number for raw_report pagination (default: 1)\n- **page_size**: Results per page for raw_report (1-100, default: 50)\n- **sort_by**: Sort for raw_report: 'cost' (default), 'clicks', 'impressions', 'conversions'\n- **min_clicks**: Minimum clicks filter for raw_report (default: 0)\n\n**Execution time:** 1-3 seconds (cached database query)\n**Data source:** search_term_daily_metrics table (updated nightly, 120-day retention)\n\n**\u26a1 RAW REPORT PAGINATION CONTRACT:**\n- When using analysis_type='raw_report', results are paginated\n- Check `pagination.has_more` \u2014 if true, call again with next `page`\n- Continue until `has_more` is false, then present consolidated results\n- Use `campaign_id` to focus on a specific campaign's search terms\n\n**Use this tool when:**\n- User asks \"what keywords should I add?\"\n- User wants to find wasted spend at keyword level\n- User asks \"what should I add as negative keywords?\"\n- User wants to improve match type efficiency\n- User asks \"which Broad keywords should be Exact?\"\n- User wants to discover hidden keyword opportunities\n- **User asks \"show me my search terms\" or \"what are people searching for?\"** \u2192 use analysis_type='raw_report'\n- **User asks \"show me search terms for campaign X\"** \u2192 use analysis_type='raw_report' with campaign_id\n\n\u26a0\ufe0f **Platform Limitation:**\nThis tool ONLY works for Google Ads. TikTok Ads does not provide search term data due to privacy restrictions.\n\n\ud83d\udcca **AFTER calling this tool, help the user understand:**\n\n**Opportunity Keywords:**\n- **What it means:** These are winning search terms you're missing\n- **Profit potential:** Shows expected additional profit if added\n- **Suggested action:** Add as new keywords with recommended bid and match type\n- **Priority:** Start with highest opportunity_value (profit potential)\n\n**Example:**\nSearch term: \"enterprise security software pricing\"\n- 8 conversions, $240 cost, $4,800 value\n- Opportunity Value: $4,560 profit potential\n- Suggested Bid: $6.00 (based on current CPC and ROAS)\n- Suggested Match Type: EXACT (high intent, proven converter)\n- **Action:** Add this as an Exact match keyword with $6 bid\n\n**Negative Keywords:**\n- **What it means:** These queries waste money and never convert\n- **Cost:** Total wasted spend on non-converting terms\n- **Common patterns:** \"free\", \"download\", \"crack\", \"review\", \"how to\"\n- **Action:** Add as negative keywords (campaign or account level)\n\n**Example:**\nSearch term: \"free security software\"\n- 120 clicks, $540 wasted, 0 conversions\n- Reason: Low purchase intent (free)\n- **Action:** Add \"free\" as negative keyword at campaign level\n\n**Match Type Performance:**\n- **Exact:** Tightest control, lowest waste, highest ROAS (recommended)\n- **Phrase:** Moderate flexibility, some waste, decent ROAS\n- **Broad:** Most volume, highest waste, lowest ROAS (use sparingly)\n\n**Example Analysis:**\n- Exact: 450 keywords, ROAS 4.8x, 2% wasted spend \u2705\n- Phrase: 280 keywords, ROAS 3.2x, 8% wasted spend \u26a0\ufe0f\n- Broad: 120 keywords, ROAS 1.8x, 45% wasted spend \ud83d\udd34\n\n**Quick Actions:**\n1. Add top 5 opportunity keywords as Exact match\n2. Block top 10 negative keywords at campaign level\n3. Convert high-performing Broad keywords to Exact\n4. Review Phrase keywords with >10% wasted spend\n\n**Visualization Tip:**\nFor opportunity keywords, suggest creating a scatter plot (x=ROAS, y=Spend, size=conversions) to visualize profit potential.\n\n**Implementation Steps:**\n1. Review top opportunities and negatives\n2. Start with 5-10 new keywords (don't overwhelm account)\n3. Add negatives in batches (test impact over 7 days)\n4. Convert Broad\u2192Exact gradually (monitor volume drop)\n5. Re-run analysis monthly to discover new opportunities\n\n**Best Practices:**\n- Focus on opportunity_value (profit) not just ROAS\n- Start with Exact match for all new keywords (tightest control)\n- Add negatives at campaign level first (easier to undo than account level)\n- Monitor match type distribution: Aim for 60%+ Exact, 30% Phrase, 10% Broad\n- Review search terms every 2 weeks during active optimization\n\n\ud83d\udcac **Community**: For keyword optimization discussions, visit our Discord: https://discord.gg/dH3Qt4YS",
        "operationId": "execute_analyze_search_terms",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "analysis_type": "all",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for search term mining and keyword opportunity analysis",
                    "properties": {
                      "analysis_type": {
                        "default": "all",
                        "description": "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'.",
                        "title": "Analysis Type",
                        "type": "string"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional campaign ID to filter search terms to a specific campaign. Get from list_campaigns.",
                        "title": "Campaign Id"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "force_refresh": {
                        "default": false,
                        "description": "If true, triggers immediate API collection from Google Ads (not yet implemented - currently uses cached data). Default is false.",
                        "title": "Force Refresh",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze search term data (7, 30, 60, 90, or 120 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "min_clicks": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 0,
                        "description": "Minimum clicks filter for raw_report. Default: 0 (show all)",
                        "title": "Min Clicks"
                      },
                      "page": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 1,
                        "description": "Page number for raw_report (1-based). Default: 1",
                        "title": "Page"
                      },
                      "page_size": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 50,
                        "description": "Results per page for raw_report (1-100). Default: 50",
                        "title": "Page Size"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "sort_by": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "cost",
                        "description": "Sort field for raw_report: 'cost' (default), 'clicks', 'impressions', 'conversions'",
                        "title": "Sort By"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_search_terms)"
                  },
                  "success": true,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_search_terms",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_search_terms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_search_terms"
      }
    },
    "/api/v1/tools/analyze_tiktok_geo_performance/execute": {
      "post": {
        "description": "Analyze TikTok geographic/country-level performance.\n\nReturns: per-country spend, conversions, CPA, CTR, hook rate, engagement. Best/worst countries with recommendations.\n\nNote: TikTok uses country-level breakdowns (not placement-level like Meta's Feed/Stories/Reels).\n\nWhen to use: \"TikTok performance by country\", \"Which countries perform best on TikTok?\", \"TikTok geo breakdown\"\n\nParameters:\n- lookback_days: default 30",
        "operationId": "execute_analyze_tiktok_geo_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_tiktok_geo_performance)"
                  },
                  "success": true,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_tiktok_geo_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_geo_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Analyze TikTok geographic/country-level performance",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_tiktok_geo_performance"
      }
    },
    "/api/v1/tools/analyze_tiktok_wasted_spend/execute": {
      "post": {
        "description": "Analyze TikTok campaigns for wasted ad spend. Identifies campaigns losing money (ROAS < 1.0) and underperforming campaigns (ROAS < target).\n\nReturns: wasted spend by campaign, severity levels (CRITICAL/HIGH/MEDIUM), creative fatigue analysis, status-aware recommendations (distinguishes active vs disabled campaigns).\n\nWhen to use: \"Where am I wasting money on TikTok?\", \"TikTok wasted spend\", \"Which TikTok campaigns are losing money?\"\n\n\u26a0\ufe0f NEVER say \"pause\" for campaigns with conversions. Say \"review\" or \"reduce budget\".\n\u26a0\ufe0f Campaigns in LEARNING phase (< 14 days) should not be paused.",
        "operationId": "execute_analyze_tiktok_wasted_spend",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "end_date": "string",
                  "include_fatigue": true,
                  "lookback_days": 30,
                  "start_date": "string",
                  "target_roas": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD",
                        "title": "End Date"
                      },
                      "include_fatigue": {
                        "default": true,
                        "description": "Include creative fatigue analysis",
                        "title": "Include Fatigue",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze: 7, 30, 60, 90, 120",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target ROAS override (e.g., 3.0)",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_tiktok_wasted_spend)"
                  },
                  "success": true,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_tiktok_wasted_spend",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_tiktok_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Analyze TikTok campaigns for wasted ad spend",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_tiktok_wasted_spend"
      }
    },
    "/api/v1/tools/analyze_wasted_spend/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAnalyze wasted ad spend and identify campaigns losing money or underperforming.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1):**\n- Identifies campaigns LOSING money (ROAS < 1.0x)\n- Identifies campaigns UNDERPERFORMING (1.0x \u2264 ROAS < target)\n- Separates true waste from opportunity cost\n- Provides severity-based classification (CRITICAL/HIGH/MEDIUM)\n- Generates actionable recommendations with expected impact\n- Shows top performing campaigns for budget reallocation\n\n**Returns detailed analysis:**\n- Total wasted spend (campaigns with ROAS < 1.0)\n- Total underperforming spend (profitable but below target)\n- Campaign-by-campaign breakdown with severity levels\n- Specific recommendations (PAUSE/REDUCE/OPTIMIZE)\n- Quick actions for immediate implementation\n- Budget reallocation suggestions\n\n**Target ROAS Resolution (3-tier priority):**\n1. Account goals table (user-set or API-pulled)\n2. 90-day historical average ROAS\n3. Default to 2.0x if no data available\n\n**Severity Levels:**\n- \ud83d\udea8 CRITICAL: ROAS < 0.5x (losing money severely)\n- \ud83d\udd34 HIGH: ROAS < 1.0x (unprofitable/breakeven)\n- \ud83d\udfe1 MEDIUM: 1.0x \u2264 ROAS < target (profitable but underperforming)\n\n**Parameters:**\n- lookback_days: 7, 30, 60, 90, or 120 days (default: 30)\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- target_roas: Optional override (e.g., 3.0 for 3.0x ROAS)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 1-3 seconds (cached database query)\n**Data source:** campaign_daily_metrics table (updated nightly)\n\n**Use this tool when:**\n- User asks \"where is my money going?\"\n- User wants to identify wasted spend\n- User wants to optimize campaign budgets\n- User asks which campaigns to pause/reduce\n- User wants to know which campaigns are losing money\n\n\ud83d\udcca **AFTER calling this tool, help the user understand:**\n\n**Wasted vs. Underperforming:**\n- **Wasted Spend** = Actual losses (ROAS < 1.0, you're losing money)\n- **Underperforming Spend** = Opportunity cost (profitable but below target)\n\n**Example:**\n- Campaign A: Spent $1000, got $600 back (ROAS 0.6x) \u2192 $400 WASTED\n- Campaign B: Spent $1000, got $1500 back (ROAS 1.5x, target 3.0x) \u2192 $0 wasted, but $500 opportunity cost\n\n**Quick Actions (IMPORTANT \u2014 read severity context first):**\n- \u23f3 LEARNING campaigns \u2192 Do NOT pause. Monitor for 14+ days before judging.\n- \u2753 INSUFFICIENT_DATA campaigns \u2192 Need more spend before analysis is meaningful.\n- \ud83d\udea8 CRITICAL campaigns (established, 14+ days, ZERO conversions) \u2192 Consider pausing\n- \ud83d\udea8 CRITICAL campaigns (established, 14+ days, HAS conversions) \u2192 Review performance, verify revenue in ad platform before reducing budget\n- \ud83d\udd34 HIGH severity (established, 14+ days) \u2192 Consider reducing budget by 50-70%\n- \ud83d\udfe1 MEDIUM \u2192 Optimize targeting, ad copy, landing pages\n- Top performers \u2192 Consider increasing budget\n\n\u26a0\ufe0f **NEVER say \"pause\" for a campaign that has conversions.** Say \"review\" or \"reduce budget\" instead.\n\u26a0\ufe0f **NEVER recommend pausing a campaign in LEARNING phase.**\n\u26a0\ufe0f **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.**\n\u26a0\ufe0f **Consider campaign objective: brand awareness campaigns will not have ROAS data. This is normal.**\n\u26a0\ufe0f **When data confidence is MEDIUM or LOW, soften all recommendations and add verification prompts.**\n\n**Visualization Tip:**\nFor 10+ campaigns, suggest creating a treemap visualization to show wasted spend by campaign size.\n\n\ud83d\udcac **Community**: For optimization discussions, visit our Discord: https://discord.gg/dH3Qt4YS",
        "operationId": "execute_analyze_wasted_spend",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string",
                  "target_roas": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 30, 60, 90, or 120 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average.",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for analyze_wasted_spend)"
                  },
                  "success": true,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: analyze_wasted_spend",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "analyze_wasted_spend"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "analyze_wasted_spend"
      }
    },
    "/api/v1/tools/associate_linkedin_conversion/execute": {
      "post": {
        "description": "User wants to add conversion tracking to a campaign.\n\nAssociate an existing conversion with a campaign.\n\nParameters:\n- campaign_id: Campaign to associate conversion with\n- conversion_id: Conversion ID to associate\n\nExecution time: 2-3 seconds",
        "operationId": "execute_associate_linkedin_conversion",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "conversion_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for associating a conversion with a campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to associate conversion with",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "conversion_id": {
                        "description": "Conversion ID to associate",
                        "title": "Conversion Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "conversion_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for associate_linkedin_conversion)"
                  },
                  "success": true,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: associate_linkedin_conversion",
                  "is_error": true,
                  "success": false,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "associate_linkedin_conversion"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to add conversion tracking to a campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "associate_linkedin_conversion"
      }
    },
    "/api/v1/tools/audit_conversion_tracking/execute": {
      "post": {
        "description": "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.\n\nNote: 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.\n\nParameters:\n- platform (optional): 'google_ads', 'meta_ads', or 'linkedin_ads'. If omitted, reviews all connected platforms.\n- lookback_days (optional): 7, 14, 30, 60, or 90 days (default: 30)\n\nExecution time: ~5-15 seconds (includes live API calls to ad platforms).",
        "operationId": "execute_audit_conversion_tracking",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "lookback_days": 30,
                  "platform": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for the conversion tracking audit tool.",
                    "properties": {
                      "lookback_days": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 30,
                        "description": "Number of days to look back for trend analysis (7, 14, 30, 60, 90). Default: 30.",
                        "title": "Lookback Days"
                      },
                      "platform": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Ad platform to audit: 'google_ads', 'meta_ads', or 'linkedin_ads'. If omitted, audits all connected platforms.",
                        "title": "Platform"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for audit_conversion_tracking)"
                  },
                  "success": true,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: audit_conversion_tracking",
                  "is_error": true,
                  "success": false,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "audit_conversion_tracking"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Review your conversion tracking setup across ad platforms",
        "tags": [
          "audit"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "audit_conversion_tracking"
      }
    },
    "/api/v1/tools/batch_update_linkedin_campaigns/execute": {
      "post": {
        "description": "User wants to update multiple LinkedIn campaigns at once (bulk operations).\n\nBatch update status, budget, or other settings across multiple campaigns simultaneously.\n\nParameters:\n- campaign_ids: List of campaign IDs to update (1-50, required)\n- status: New status for all campaigns (ACTIVE, PAUSED, or ARCHIVED)\n- daily_budget: New daily budget for all campaigns (minimum $10)\n- total_budget: New total budget for all campaigns\n- account_id: Optional LinkedIn Ad Account ID\n\nExample Prompts:\n- \"Pause all my LinkedIn campaigns\"\n- \"Set all campaigns to $20/day budget\"\n- \"Archive campaigns 123, 456, and 789\"\n- \"Activate these 5 campaigns\"\n- \"Update budget for all my LinkedIn ads\"\n- \"Bulk pause my campaigns\"\n\nExecution time: 5-15 seconds (depends on number of campaigns)",
        "operationId": "execute_batch_update_linkedin_campaigns",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_ids": [
                    "string"
                  ],
                  "daily_budget": 10.0,
                  "status": "string",
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for batch updating campaigns",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_ids": {
                        "description": "List of campaign IDs to update (1-50)",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 50,
                        "minItems": 1,
                        "title": "Campaign Ids",
                        "type": "array"
                      },
                      "daily_budget": {
                        "anyOf": [
                          {
                            "minimum": 10.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget for all campaigns",
                        "title": "Daily Budget"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: ACTIVE, PAUSED, or ARCHIVED",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New total budget for all campaigns",
                        "title": "Total Budget"
                      }
                    },
                    "required": [
                      "campaign_ids"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for batch_update_linkedin_campaigns)"
                  },
                  "success": true,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: batch_update_linkedin_campaigns",
                  "is_error": true,
                  "success": false,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "batch_update_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to update multiple LinkedIn campaigns at once (bulk operations) [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "batch_update_linkedin_campaigns"
      }
    },
    "/api/v1/tools/browse_meta_targeting/execute": {
      "post": {
        "description": "User wants to browse all targeting options in a specific category without a search query.\n\nThis tool retrieves all options in a Meta targeting category for exploration and discovery.\n\nReturns:\n- List of all targeting options in the category\n- IDs, names, and audience sizes\n- Useful for discovering targeting options when user doesn't have a specific query\n\nWhen to use this tool:\n- \"Show me all available behaviors for Meta targeting\"\n- \"What interest categories are available?\"\n- \"Browse demographic targeting options\"\n- \"List all life events I can target\"\n- \"What income targeting options exist?\"\n\nCategories Available:\n- interests: All interest targeting categories\n- behaviors: All behavior targeting options\n- demographics: All demographic targeting options\n- life_events: All life event targeting options\n- industries: Industry targeting options\n- income: Income bracket targeting options\n- family_statuses: Family status targeting options\n\nParameters:\n- category: Category to browse (required)\n- limit: Maximum results (1-500, default: 100)\n- locale: Locale for results (default: en_US)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 1-3 seconds\nData source: Meta Marketing API Targeting Search",
        "operationId": "execute_browse_meta_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "category": "string",
                  "limit": 100,
                  "locale": "en_US"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for browsing Meta targeting categories",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "category": {
                        "description": "Category to browse:\n- 'interests': Browse all interest categories\n- 'behaviors': Browse all behavior categories\n- 'demographics': Browse all demographic categories\n- 'life_events': Browse all life event categories\n- 'industries': Browse industry targeting options\n- 'income': Browse income targeting options\n- 'family_statuses': Browse family status targeting options",
                        "title": "Category",
                        "type": "string"
                      },
                      "limit": {
                        "default": 100,
                        "description": "Maximum number of results to return (1-500). Default is 100.",
                        "title": "Limit",
                        "type": "integer"
                      },
                      "locale": {
                        "default": "en_US",
                        "description": "Locale for results (e.g., 'en_US', 'es_ES'). Default is 'en_US'.",
                        "title": "Locale",
                        "type": "string"
                      }
                    },
                    "required": [
                      "category"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for browse_meta_targeting)"
                  },
                  "success": true,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: browse_meta_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "browse_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to browse all targeting options in a specific category without a search query",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "browse_meta_targeting"
      }
    },
    "/api/v1/tools/clone_linkedin_campaign/execute": {
      "post": {
        "description": "User wants to duplicate, copy, or clone a LinkedIn campaign.\n\nClone an existing campaign with optional overrides for name, budget, locations, and creatives.\n\nParameters:\n- source_campaign_id: Campaign ID to clone (required)\n- new_name: Name for the cloned campaign (default: '{original} - Copy')\n- daily_budget: Override daily budget for the clone\n- locations: Override location targeting for the clone\n- status: Status for new campaign (default: PAUSED for safety)\n- copy_creatives: Copy creatives from source (default: True)\n- account_id: Optional LinkedIn Ad Account ID\n\nThe cloned campaign is always created as PAUSED for safety. Activate it when ready.\n\nExample Prompts:\n- \"Clone my LinkedIn campaign\"\n- \"Duplicate campaign 12345 with a new name\"\n- \"Copy my campaign but change the budget to $30/day\"\n- \"Create a copy of my campaign for a different region\"\n- \"Replicate campaign with different targeting\"\n\nExecution time: 5-10 seconds",
        "operationId": "execute_clone_linkedin_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "copy_creatives": true,
                  "daily_budget": 10.0,
                  "locations": [
                    "string"
                  ],
                  "new_name": "string",
                  "source_campaign_id": "<campaign_id>",
                  "status": "PAUSED"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for cloning a campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "copy_creatives": {
                        "default": true,
                        "description": "Copy creatives from source campaign",
                        "title": "Copy Creatives",
                        "type": "boolean"
                      },
                      "daily_budget": {
                        "anyOf": [
                          {
                            "minimum": 10.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget for clone",
                        "title": "Daily Budget"
                      },
                      "locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override locations for cloned campaign",
                        "title": "Locations"
                      },
                      "new_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for cloned campaign (default: '{original} - Copy')",
                        "title": "New Name"
                      },
                      "source_campaign_id": {
                        "description": "Campaign ID to clone",
                        "title": "Source Campaign Id",
                        "type": "string"
                      },
                      "status": {
                        "default": "PAUSED",
                        "description": "Status for new campaign (default PAUSED for safety)",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "source_campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for clone_linkedin_campaign)"
                  },
                  "success": true,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: clone_linkedin_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "clone_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to duplicate, copy, or clone a LinkedIn campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "clone_linkedin_campaign"
      }
    },
    "/api/v1/tools/create_ad/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nCreate a new Responsive Search Ad (RSA) in an existing ad group.\n\nUseful for A/B testing different ad copy or adding variation to an ad group.\n\n**Parameters:**\n- ad_group_id: The ad group to add the ad to (REQUIRED)\n- headlines: 3-15 headlines (REQUIRED, max 30 chars each)\n- descriptions: 2-4 descriptions (REQUIRED, max 90 chars each)\n- final_urls: Landing page URLs (REQUIRED, at least one)\n- path1: Optional display path 1 (max 15 chars)\n- path2: Optional display path 2 (max 15 chars)\n- customer_id: Optional\n\n**Execution time:** 3-5 seconds\n\n**New ad goes through Google's review process.**\n\n**Example:**\nUser: \"Create a new ad to test different messaging\"\nAgent:\n1. Gets ad_group_id from get_campaign_structure\n2. Prepares headlines and descriptions\n3. Creates new ad with create_ad",
        "operationId": "execute_create_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "customer_id": "string",
                  "descriptions": [
                    "string"
                  ],
                  "final_urls": [
                    "string"
                  ],
                  "headlines": [
                    "string"
                  ],
                  "path1": "string",
                  "path2": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating a new RSA",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID to add the ad to. Get from get_campaign_structure.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "description": "List of 2-4 descriptions. Each max 90 characters.",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 4,
                        "minItems": 2,
                        "title": "Descriptions",
                        "type": "array"
                      },
                      "final_urls": {
                        "description": "Landing page URLs. At least one required.",
                        "items": {
                          "type": "string"
                        },
                        "minItems": 1,
                        "title": "Final Urls",
                        "type": "array"
                      },
                      "headlines": {
                        "description": "List of 3-15 headlines. Each max 30 characters. Include mix of: product identity, benefits, and CTAs.",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 15,
                        "minItems": 3,
                        "title": "Headlines",
                        "type": "array"
                      },
                      "path1": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional display path 1 (max 15 chars). Shows in ad URL.",
                        "title": "Path1"
                      },
                      "path2": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional display path 2 (max 15 chars). Shows after path1.",
                        "title": "Path2"
                      }
                    },
                    "required": [
                      "ad_group_id",
                      "headlines",
                      "descriptions",
                      "final_urls"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_ad)"
                  },
                  "success": true,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_ad"
      }
    },
    "/api/v1/tools/create_demandgen_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\n\ud83d\udd04 LONG-RUNNING TOOL: Creates a Demand Gen campaign across ALL Google channels \u2014 YouTube, Discover, Gmail, Display, and Maps. Emits MCP progress updates during creation (typically 10-20 seconds).\n\n\u26a0\ufe0f CRITICAL WARNING \u26a0\ufe0f\n- Call this tool ONLY ONCE per campaign\n- Creates REAL campaigns that cost REAL money\n- Do NOT retry automatically if errors occur\n- Report errors to user instead of retrying\n\n\ud83c\udf10 **Demand Gen Campaign Channels (ALL enabled by default):**\n\u2705 YouTube In-Feed (search results & related videos)\n\u2705 YouTube In-Stream (before/during/after videos)\n\u2705 YouTube Shorts (Shorts feed)\n\u2705 Gmail (Promotions & Social tabs)\n\u2705 Discover (Google Discover feed)\n\u2705 Display (Google Display Network)\n\u274c Maps (opt-in, disabled by default)\n\n\ud83d\udccb **YOUR CRITICAL ROLE: Campaign Strategist & Text Creator**\n\n**STEP 1: Collect Campaign Details from User**\n\nYOU MUST collect these from the user:\n1. **Campaign Name** - Descriptive and unique\n2. **Daily Budget** - In account's native currency (recommended $20+/day)\n3. **Ad Format** - multi_asset (images) or video_responsive (videos)\n4. **Final URL** - Landing page\n5. **Business Name** - Max 25 characters\n\n**STEP 2: Discover Existing Assets**\n\ud83d\udd34 MANDATORY: Call discover_existing_assets to find existing logos/images.\n- If logos found \u2192 use logo_asset_id or existing_images.logos_square\n- If NO logos \u2192 ask user for logo URL, run validate_and_prepare_assets\n\n**STEP 3: Generate Ad Copy**\n- Headlines (1-5): STRICT 40 character max each\n- Descriptions (1-5): STRICT 90 character max each\n- Long Headlines (optional, for video_responsive): 90 char max each\n\n**STEP 4: Prepare Images/Videos**\nFor multi_asset: Need landscape (1.91:1) OR square (1:1) marketing images + logo\nFor video_responsive: Need YouTube video IDs (validate first) + logo\n\n**STEP 5: Call create_demandgen_campaign with complete payload**\n\n**Bidding Options (your choice is honored \u2014 engine is advisory only):**\n- MAXIMIZE_CLICKS: Default, works without conversion tracking\n- MAXIMIZE_CONVERSIONS: Requires conversion tracking\n- TARGET_CPA: Requires target_cpa value\n- MAXIMIZE_CONVERSION_VALUE: Requires conversion tracking\n- TARGET_ROAS: Requires target_roas value\n\n**Channel Controls (optional):**\nPass channels dict to disable specific channels:\n{\"gmail\": false, \"display\": false} \u2192 YouTube + Discover only\n\n**STEP 6: MANDATORY \u2014 Add Extensions After Creation**\nAfter success, add sitelinks, callouts, and structured snippets.\n\n**Execution Time:** 10-20 seconds\n\n**CRITICAL REMINDERS:**\n- Campaign starts PAUSED for user safety\n- This costs real money \u2014 be transparent\n- Never retry on failure \u2014 report error to user",
        "operationId": "execute_create_demandgen_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_format": "multi_asset",
                  "bidding_strategy": "MAXIMIZE_CLICKS",
                  "budget_daily": 1.0,
                  "business_name": "string",
                  "call_to_action": "string",
                  "campaign_name": "string",
                  "descriptions": [
                    "string"
                  ],
                  "final_url": "https://example.com",
                  "headlines": [
                    "string"
                  ],
                  "long_headlines": [
                    "string"
                  ],
                  "target_languages": [
                    "string"
                  ],
                  "target_locations": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for create_demandgen_campaign tool.\n\nCreates a Demand Gen campaign across ALL Google channels:\nYouTube (In-Feed, In-Stream, Shorts), Discover, Gmail, Display, and Maps.",
                    "properties": {
                      "ad_format": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "multi_asset",
                        "description": "Ad format: 'multi_asset' (image ads, default), 'video_responsive' (video ads). multi_asset requires marketing images. video_responsive requires YouTube video IDs.",
                        "title": "Ad Format"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID (UUID) from validate_and_prepare_assets.",
                        "title": "Asset Bundle Id"
                      },
                      "audience_segments": {
                        "anyOf": [
                          {
                            "additionalProperties": true,
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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, skip audience_segments entirely. Keys: 'in_market_audience_ids' (List[int] -- people actively researching), 'affinity_audience_ids' (List[int] -- people with long-term interests), 'custom_audience_ids' (List[str] -- custom audience resource names from search_audiences), 'user_list_ids' (List[str] -- remarketing/customer match resource names). Example: {'in_market_audience_ids': [80463, 80520], 'affinity_audience_ids': [92913]}",
                        "title": "Audience Segments"
                      },
                      "bidding_strategy": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "MAXIMIZE_CLICKS",
                        "description": "Bidding strategy: MAXIMIZE_CLICKS (default), MAXIMIZE_CONVERSIONS, TARGET_CPA, MAXIMIZE_CONVERSION_VALUE, TARGET_ROAS. Your explicit choice is honored \u2014 the engine is advisory only.",
                        "title": "Bidding Strategy"
                      },
                      "budget_daily": {
                        "description": "Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies \u2014 pass the user's amount as-is. Example: if user says '\u20b91000/day', pass 1000 (not a USD conversion). Google recommends minimum ~$20/day USD equivalent for Demand Gen campaigns.",
                        "minimum": 1.0,
                        "title": "Budget Daily",
                        "type": "number"
                      },
                      "business_name": {
                        "description": "Business name, max 25 characters. Example: 'Adspirer'",
                        "maxLength": 25,
                        "title": "Business Name",
                        "type": "string"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Call-to-action label. Options: LEARN_MORE, SHOP_NOW, SIGN_UP, SUBSCRIBE, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_QUOTE, APPLY_NOW",
                        "title": "Call To Action"
                      },
                      "campaign_name": {
                        "description": "Campaign name (e.g., 'DemandGen Summer Promo 2026')",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "channels": {
                        "anyOf": [
                          {
                            "additionalProperties": {
                              "type": "boolean"
                            },
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Channels"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "description": "MUST BE JSON ARRAY: 1-5 descriptions, each EXACTLY 90 characters maximum. Example: [\"Manage ads with AI - no dashboard needed.\"]. COUNT CHARACTERS BEFORE CALLING!",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 5,
                        "minItems": 1,
                        "title": "Descriptions",
                        "type": "array"
                      },
                      "existing_images": {
                        "anyOf": [
                          {
                            "additionalProperties": {
                              "items": {
                                "type": "string"
                              },
                              "type": "array"
                            },
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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/456\"]}",
                        "title": "Existing Images"
                      },
                      "final_url": {
                        "description": "Landing page URL (must match verified domain). Example: 'https://example.com/product'",
                        "title": "Final Url",
                        "type": "string"
                      },
                      "headlines": {
                        "description": "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!",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 5,
                        "minItems": 1,
                        "title": "Headlines",
                        "type": "array"
                      },
                      "logo_asset_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Logo image asset ID from discover_existing_assets.",
                        "title": "Logo Asset Id"
                      },
                      "logo_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New logo images as [{\"url\": \"https://...\", \"name\": \"logo1\"}].",
                        "title": "Logo Images"
                      },
                      "long_headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "maxItems": 5,
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Long Headlines"
                      },
                      "marketing_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Landscape images (1.91:1) as [{\"url\": \"https://...\", \"name\": \"img1\"}]. At least landscape OR square images required for multi_asset.",
                        "title": "Marketing Images"
                      },
                      "portrait_marketing_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Portrait images (4:5) as [{\"url\": \"https://...\", \"name\": \"img1\"}]. Optional.",
                        "title": "Portrait Marketing Images"
                      },
                      "square_marketing_images": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Square images (1:1) as [{\"url\": \"https://...\", \"name\": \"img1\"}].",
                        "title": "Square Marketing Images"
                      },
                      "target_cpa": {
                        "anyOf": [
                          {
                            "minimum": 0.01,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target CPA in account's native currency (only for TARGET_CPA bidding). Example: 25.0",
                        "title": "Target Cpa"
                      },
                      "target_languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Language targets as ISO codes (e.g., ['en', 'es']). Defaults to English.",
                        "title": "Target Languages"
                      },
                      "target_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Geographic targets globally (e.g., ['India', 'United States', 'London']). Supports countries, states, cities worldwide. Defaults to United States.",
                        "title": "Target Locations"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "minimum": 0.01,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target ROAS multiplier (only for TARGET_ROAS bidding). Example: 3.5 means 350% return on ad spend.",
                        "title": "Target Roas"
                      },
                      "youtube_video_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "YouTube video IDs (each 11 chars) for video_responsive format. 1-5 videos. Must be validated first using validate_video tool.",
                        "title": "Youtube Video Ids"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "budget_daily",
                      "final_url",
                      "business_name",
                      "headlines",
                      "descriptions"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_demandgen_campaign)"
                  },
                  "success": true,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_demandgen_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_demandgen_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_demandgen_campaign"
      }
    },
    "/api/v1/tools/create_linkedin_carousel_campaign/execute": {
      "post": {
        "description": "**USE THIS TOOL WHEN:** User wants to create a LinkedIn CAROUSEL ad campaign (2-10 swipeable image cards).\n\n**DO NOT USE for image ads** \u2192 use `create_linkedin_image_campaign` instead.\n**DO NOT USE for video ads** \u2192 use `create_linkedin_video_campaign` instead.\n**DO NOT USE for text ads** \u2192 use `create_linkedin_text_campaign` instead.\n\n**REQUIRED:** Either `campaign_group_name` (creates new group) or `campaign_group_id` (adds to existing group).\n\nIMPORTANT: This creates the campaign with 1 creative (Variation 1). Campaign is created in PAUSED status.\n\nCarousel Specifications:\n- 2-10 cards required (3-5 is the sweet spot)\n- All images must be 1080x1080 (1:1 aspect ratio)\n- Each card has: image, headline (max 45 chars), landing page URL\n- Introductory text max: 255 characters\n\nRequired Parameters:\n- campaign_name, daily_budget, organization_id\n- introductory_text (up to 255 chars), landing_page_url (overall)\n- cards: Array of 2-10 cards, each with image (urn or url), headline, landing_page_url\n- locations (targeting)\n- campaign_group_name OR campaign_group_id\n\nDefault Objective: WEBSITE_VISIT\n\nAD POLICY: NEVER use \"LinkedIn\" in ad copy. NEVER mention competitor platforms.\n\n**After Creation \u2014 IMPORTANT:**\n- This tool created 1 campaign + 1 creative. Campaign Group ID is returned.\n- To add MORE carousel creatives (A/B test text): use `add_linkedin_carousel_creative` with the campaign_id\n- To add MORE campaigns (different audience): use `add_linkedin_campaign_to_group` with the campaign_group_id\n- NEVER call this create tool again without campaign_group_id \u2014 that creates a SEPARATE campaign group\n\nExecution time: 15-60 seconds (includes image uploads)",
        "operationId": "execute_create_linkedin_carousel_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "call_to_action": "LEARN_MORE",
                  "campaign_group_id": "string",
                  "campaign_group_name": "string",
                  "campaign_name": "string",
                  "cards": [
                    {
                      "headline": "string",
                      "image_url": "string",
                      "image_urn": "string",
                      "landing_page_url": "https://example.com"
                    }
                  ],
                  "daily_budget": 10.0,
                  "industries": [
                    "string"
                  ],
                  "introductory_text": "string",
                  "landing_page_url": "https://example.com",
                  "locations": [
                    "string"
                  ],
                  "objective": "WEBSITE_VISIT",
                  "organization_id": "string",
                  "seniorities": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "$defs": {
                      "CarouselCardInput": {
                        "description": "Input schema for a single carousel card",
                        "properties": {
                          "headline": {
                            "description": "Card headline (max 45 characters, required).",
                            "maxLength": 45,
                            "title": "Headline",
                            "type": "string"
                          },
                          "image_url": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Public URL to download and upload image. Must be 1080x1080.",
                            "title": "Image Url"
                          },
                          "image_urn": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Existing image URN (urn:li:image:...). Must be 1080x1080.",
                            "title": "Image Urn"
                          },
                          "landing_page_url": {
                            "description": "Card-specific landing page URL. Must be HTTPS.",
                            "title": "Landing Page Url",
                            "type": "string"
                          }
                        },
                        "required": [
                          "headline",
                          "landing_page_url"
                        ],
                        "title": "CarouselCardInput",
                        "type": "object"
                      }
                    },
                    "description": "Input schema for creating LinkedIn Carousel Ad campaigns",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of age range URNs.",
                        "title": "Age Ranges"
                      },
                      "buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of buyer group URNs (API 2026-03+).",
                        "title": "Buyer Groups"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "Call-to-action button label.",
                        "title": "Call To Action"
                      },
                      "campaign_group_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing Campaign Group ID to add this campaign to. If provided, campaign_group_name is NOT needed.",
                        "title": "Campaign Group Id"
                      },
                      "campaign_group_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). Example: 'Q2 Carousel Ads'. Max 100 characters.",
                        "title": "Campaign Group Name"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "cards": {
                        "description": "List of 2-10 carousel cards. Each card needs: image (urn or url, 1080x1080), headline (max 45 chars), landing_page_url (HTTPS).",
                        "items": {
                          "$ref": "#/$defs/CarouselCardInput"
                        },
                        "maxItems": 10,
                        "minItems": 2,
                        "title": "Cards",
                        "type": "array"
                      },
                      "company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of company size URNs.",
                        "title": "Company Sizes"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for the first creative. E.g. 'Product Showcase - Carousel 1'. Defaults to '{campaign_name} - Ad 1'.",
                        "title": "Creative Name"
                      },
                      "currency": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "USD",
                        "description": "Currency code. Default: USD.",
                        "title": "Currency"
                      },
                      "daily_budget": {
                        "description": "Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD.",
                        "minimum": 10.0,
                        "title": "Daily Budget",
                        "type": "number"
                      },
                      "degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of degree URNs.",
                        "title": "Degrees"
                      },
                      "employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of employer URNs.",
                        "title": "Employers"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional end date in ISO format.",
                        "title": "End Date"
                      },
                      "fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of field of study URNs.",
                        "title": "Fields Of Study"
                      },
                      "followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of followed company URNs.",
                        "title": "Followed Companies"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of gender URNs.",
                        "title": "Genders"
                      },
                      "industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of industry URNs.",
                        "title": "Industries"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of interest URNs.",
                        "title": "Interests"
                      },
                      "introductory_text": {
                        "description": "Main ad text (up to 255 characters for carousel ads).",
                        "maxLength": 255,
                        "title": "Introductory Text",
                        "type": "string"
                      },
                      "job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job function URNs.",
                        "title": "Job Functions"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job title URNs.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Overall carousel landing page URL. Must be HTTPS.",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "locale_country": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "US",
                        "description": "Campaign locale country (ISO-3166). Default: US.",
                        "title": "Locale Country"
                      },
                      "locale_language": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "en",
                        "description": "Campaign locale language (ISO-639). Default: en.",
                        "title": "Locale Language"
                      },
                      "locations": {
                        "description": "List of LinkedIn location URNs to target. Required.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Locations",
                        "type": "array"
                      },
                      "member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of member behavior URNs.",
                        "title": "Member Behaviors"
                      },
                      "member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of LinkedIn group URNs.",
                        "title": "Member Groups"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "WEBSITE_VISIT",
                        "description": "Campaign objective. Default: WEBSITE_VISIT. Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION.",
                        "title": "Objective"
                      },
                      "organization_id": {
                        "description": "LinkedIn Organization (Company Page) ID.",
                        "title": "Organization Id",
                        "type": "string"
                      },
                      "schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of school URNs.",
                        "title": "Schools"
                      },
                      "seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of seniority level URNs.",
                        "title": "Seniorities"
                      },
                      "skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of skill URNs.",
                        "title": "Skills"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional start date in ISO format.",
                        "title": "Start Date"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "PAUSED",
                        "description": "Initial campaign status. Default: PAUSED.",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional total lifetime budget.",
                        "title": "Total Budget"
                      },
                      "years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of years of experience URNs.",
                        "title": "Years Of Experience"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "daily_budget",
                      "organization_id",
                      "cards",
                      "introductory_text",
                      "landing_page_url",
                      "locations"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_linkedin_carousel_campaign)"
                  },
                  "success": true,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_linkedin_carousel_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to create a LinkedIn CAROUSEL ad campaign (2-10 swipeable image cards) [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_linkedin_carousel_campaign"
      }
    },
    "/api/v1/tools/create_linkedin_image_campaign/execute": {
      "post": {
        "description": "\u26a0\ufe0f STOP - DO NOT CALL THIS TOOL DIRECTLY!\n\nThis tool creates REAL LinkedIn campaigns that cost REAL money.\nYou MUST follow the MANDATORY 9-PHASE WORKFLOW below before calling this tool.\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\nMANDATORY 9-PHASE WORKFLOW - MUST COMPLETE ALL PHASES WITH USER CONFIRMATION\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\nPHASE 1: DISCOVERY (required)\n\u2713 Call `get_linkedin_organizations` to get Organization ID and Account ID\n\u2713 Call `research_business_for_linkedin_targeting` with user's website URL\n\u2713 Ask user about their business, products, and ideal customer\n\nPHASE 2: TARGETING STRATEGY (required - present ALL options to user)\n\u2713 Ask user which LOCATIONS to target (show common options: US, UK, Canada, EU, etc.)\n\u2713 Present SENIORITY levels with recommendations:\n  - Unpaid (urn:li:seniority:1), Training (urn:li:seniority:2), Entry (urn:li:seniority:3)\n  - Senior (urn:li:seniority:4), Manager (urn:li:seniority:5), Director (urn:li:seniority:6)\n  - VP (urn:li:seniority:7), CXO (urn:li:seniority:8), Partner (urn:li:seniority:9), Owner (urn:li:seniority:10)\n  - Recommend for B2B: Manager, Director, VP, CXO\n\u2713 Present ALL COMPANY SIZE tiers (use staffCountRange URNs):\n  - 1 employee: urn:li:staffCountRange:(1,1)\n  - 2-10: urn:li:staffCountRange:(2,10), 11-50: urn:li:staffCountRange:(11,50)\n  - 51-200: urn:li:staffCountRange:(51,200), 201-500: urn:li:staffCountRange:(201,500)\n  - 501-1000: urn:li:staffCountRange:(501,1000), 1001-5000: urn:li:staffCountRange:(1001,5000)\n  - 5001-10000: urn:li:staffCountRange:(5001,10000), 10000+: urn:li:staffCountRange:(10001,2147483647)\n\u2713 Suggest INDUSTRIES based on research\n\u2713 GET USER CONFIRMATION on targeting before proceeding\n\nPHASE 3: CAMPAIGN OBJECTIVE (required - explain ALL options)\n\u2713 Call `explain_linkedin_objectives` or present this table to user:\n\n| Objective | Best For | Cost |\n|-----------|----------|------|\n| WEBSITE_VISIT \u2b50 | Traffic to website | $5-15 CPC |\n| WEBSITE_CONVERSIONS | Sign-ups, purchases | $50-200 CPA |\n| LEAD_GENERATION | LinkedIn lead forms | $30-100 CPL |\n| BRAND_AWARENESS | Reach & impressions | $8-15 CPM |\n| ENGAGEMENT | Likes, shares | $10-20 CPM |\n| VIDEO_VIEWS | Video content | $0.05-0.20 CPV |\n\n\u2713 Recommend ONE objective with reasoning based on user's business\n\u2713 GET USER CONFIRMATION on objective\n\nPHASE 4: BUDGET & SCHEDULE (required)\n\u2713 Ask user for DAILY BUDGET (minimum $10, recommend $30-50 for testing)\n\u2713 Ask for CAMPAIGN DURATION (minimum 7 days for learning phase)\n\u2713 Ask for START DATE (immediate or scheduled)\n\u2713 GET USER CONFIRMATION\n\nPHASE 5: CONVERSION TRACKING (optional but recommended)\n\u2713 Call `list_linkedin_conversions` to check existing conversions\n\u2713 Present existing conversions to user\n\u2713 Ask which conversions to track (or skip if none)\n\nPHASE 6: CREATIVE ASSETS (required)\n\u2713 Call `discover_linkedin_assets` to find existing images\n\u2713 Present available images to user with descriptions\n\u2713 Ask user to SELECT an existing image OR upload new one\n\u2713 If uploading: Use `validate_and_prepare_linkedin_assets`\n\nPHASE 7: AD CREATIVE COPY (required - create 4 variations)\n\u2713 Call `generate_linkedin_ad_creatives` to create 4 variations:\n  1. Problem-focused (pain point)\n  2. Solution-focused (benefit)\n  3. Social proof (results/testimonials)\n  4. Curiosity/question hook\n\u2713 Present ALL 4 variations to user\n\u2713 Each variation needs: Introductory text, Headline, CTA\n\u2713 GET USER APPROVAL on ad copy\n\nPHASE 8: FINAL CONFIRMATION (required)\n\u2713 Present COMPLETE CAMPAIGN SUMMARY:\n  - Campaign name, objective, daily budget, duration\n  - All targeting settings\n  - Ad creative content\n  - Landing page URL\n  - Estimated reach and costs\n\u2713 Ask user: \"Ready to create? Type 'Yes' to proceed\"\n\u2713 DO NOT PROCEED without explicit user confirmation\n\nPHASE 9: CREATE CAMPAIGN + ALL VARIATIONS (only after all confirmations)\n\u2713 Call this tool with Variation 1 parameters to create the campaign\n\u2713 Then call `add_linkedin_creative` for each remaining variation (2, 3, 4) using the campaign_id\n\u2713 Campaign is created PAUSED by default - ask user if they want to activate\n\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\nCRITICAL RULES:\n1. NEVER skip phases - complete ALL 9 phases in order\n2. NEVER assume user preferences - ALWAYS ask\n3. NEVER call this tool without explicit final confirmation\n4. ALWAYS show all options (seniorities, company sizes, objectives)\n5. ALWAYS present 4-5 ad creative variations to user BEFORE creating\n6. ALWAYS create ALL approved variations (use `add_linkedin_creative` after initial creation)\n7. ALWAYS present full summary before creating\n8. ALWAYS provide a `creative_name` for each creative (e.g. \"Campaign Name - Ad 1\", \"Campaign Name - Ad 2\")\n9. NEVER use \"LinkedIn\" in ad copy (introductory text, headline). LinkedIn's ad policy prohibits it. Also avoid other platform names (Facebook, Google, Instagram, etc.)\n\nRequired Parameters:\n- campaign_name: Descriptive name (auto-suffixed with timestamp)\n- daily_budget: Minimum $10/day\n- organization_id: LinkedIn Company Page ID (from Phase 1)\n- introductory_text: Main ad copy (up to 600 chars)\n- landing_page_url: HTTPS URL\n- locations: List of location URNs (required)\n- Image source: image_urn OR asset_bundle_id (from Phase 6)\n\nOptional Parameters:\n- headline, call_to_action, objective\n- industries, seniorities, job_titles, company_sizes\n- start_date, end_date, total_budget\n\nLocation URNs:\n- United States: urn:li:geo:103644278\n- United Kingdom: urn:li:geo:101165590\n- Canada: urn:li:geo:101174742\n- Australia: urn:li:geo:101452733\n- Germany: urn:li:geo:101282230\n- India: urn:li:geo:102713980\n\nExecution time: 15-30 seconds",
        "operationId": "execute_create_linkedin_image_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "asset_bundle_id": "string",
                  "call_to_action": "LEARN_MORE",
                  "campaign_group_id": "string",
                  "campaign_group_name": "string",
                  "campaign_name": "string",
                  "daily_budget": 10.0,
                  "headline": "string",
                  "image_urn": "string",
                  "introductory_text": "string",
                  "landing_page_url": "https://example.com",
                  "locations": [
                    "string"
                  ],
                  "objective": "WEBSITE_VISIT",
                  "organization_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating LinkedIn Single Image Ad campaigns",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional - will use primary account if not provided)",
                        "title": "Account Id"
                      },
                      "age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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).",
                        "title": "Age Ranges"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID from validate_and_prepare_linkedin_assets tool. Use this for NEW image uploads. Mutually exclusive with image_urn.",
                        "title": "Asset Bundle Id"
                      },
                      "buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of buyer group URNs (API 2026-03+). Example: ['urn:li:standardizedProductCategory:1031']. Target B2B buyers in specific product categories.",
                        "title": "Buyer Groups"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "Call-to-action button label. Options: APPLY, DOWNLOAD, VIEW_QUOTE, LEARN_MORE, SIGN_UP,SUBSCRIBE, REGISTER, JOIN, ATTEND, REQUEST_DEMO. Default: LEARN_MORE.",
                        "title": "Call To Action"
                      },
                      "campaign_group_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 or urn:li:sponsoredCampaignGroup:XXXXX. If provided, campaign_group_name is NOT needed.",
                        "title": "Campaign Group Id"
                      },
                      "campaign_group_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Campaign Group Name"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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: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). Note: staffCountRanges may NOT be AND'ed with employers targeting.",
                        "title": "Company Sizes"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for the first creative (shown in Campaign Manager UI). E.g. 'Summer Sale - Ad 1', 'Long Form \u2014 Problem Hook'. If not provided, defaults to '{campaign_name} - Ad 1'.",
                        "title": "Creative Name"
                      },
                      "currency": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "USD",
                        "description": "Currency code for budget. Default: USD.",
                        "title": "Currency"
                      },
                      "daily_budget": {
                        "description": "Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD.",
                        "minimum": 10.0,
                        "title": "Daily Budget",
                        "type": "number"
                      },
                      "degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of degree URNs. Example: ['urn:li:degree:700'].",
                        "title": "Degrees"
                      },
                      "employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of employer (organization) URNs. Example: ['urn:li:organization:1035']. Note: employers may NOT be AND'ed with industries or staffCountRanges.",
                        "title": "Employers"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional campaign end date in ISO format. If not provided, campaign runs indefinitely until paused.",
                        "title": "End Date"
                      },
                      "fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of field of study URNs. Example: ['urn:li:fieldOfStudy:100275'].",
                        "title": "Fields Of Study"
                      },
                      "followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of followed company (organization) URNs. Target members who follow specific companies.",
                        "title": "Followed Companies"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of gender URNs. Include-only facet. Values: urn:li:gender:MALE, urn:li:gender:FEMALE.",
                        "title": "Genders"
                      },
                      "headline": {
                        "description": "Ad headline (up to 70 characters, REQUIRED). Appears below the image. Keep it concise and action-oriented. Ads without headlines perform significantly worse.",
                        "maxLength": 70,
                        "title": "Headline",
                        "type": "string"
                      },
                      "image_urn": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Image Urn"
                      },
                      "industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Industries"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of interest URNs. Example: ['urn:li:interest:689290']. Target members based on interests expressed on LinkedIn.",
                        "title": "Interests"
                      },
                      "introductory_text": {
                        "description": "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.",
                        "maxLength": 600,
                        "title": "Introductory Text",
                        "type": "string"
                      },
                      "job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job function URNs. Example: ['urn:li:function:22']. Note: job functions may NOT be AND'ed with job titles.",
                        "title": "Job Functions"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Destination URL where users will be directed. Must be HTTPS. LinkedIn tracks clicks and conversions through this URL.",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "locale_country": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "US",
                        "description": "Campaign locale country code (ISO-3166 uppercase). Default: US. Examples: US, GB, CA, DE, FR, IN, AU, BR, JP.",
                        "title": "Locale Country"
                      },
                      "locale_language": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "en",
                        "description": "Campaign locale language code (ISO-639 lowercase). Default: en. Examples: en, de, fr, es, pt, ja, zh.",
                        "title": "Locale Language"
                      },
                      "locations": {
                        "description": "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:102095887, New York City=urn:li:geo:102571732, UK=urn:li:geo:101165590, Canada=urn:li:geo:101174742, Germany=urn:li:geo:101282230.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Locations",
                        "type": "array"
                      },
                      "member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of member behavior URNs. Example: ['urn:li:memberBehavior:2'] (Mobile Users). Note: may NOT be AND'ed with contact/website retargeting segments.",
                        "title": "Member Behaviors"
                      },
                      "member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of LinkedIn group URNs. Example: ['urn:li:group:1234']. Include-only facet. Disabled in EEA/CH since May 2024.",
                        "title": "Member Groups"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "WEBSITE_VISIT",
                        "description": "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 supported for Single Image Ads - it requires Lead Gen Forms.",
                        "title": "Objective"
                      },
                      "organization_id": {
                        "description": "LinkedIn Organization (Company Page) ID. The organization that will appear as the ad author. Must be a Company Page you have admin access to.",
                        "title": "Organization Id",
                        "type": "string"
                      },
                      "schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of school (organization) URNs. Example: ['urn:li:organization:1035'].",
                        "title": "Schools"
                      },
                      "seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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, Owner=urn:li:seniority:10. Note: seniorities may NOT be AND'ed with job titles targeting.",
                        "title": "Seniorities"
                      },
                      "skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of skill URNs. Example: ['urn:li:skill:17']. Use search_linkedin_targeting to discover skill URNs.",
                        "title": "Skills"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional campaign start date in ISO format (e.g., '2024-01-15T00:00:00Z'). If not provided, campaign starts immediately.",
                        "title": "Start Date"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "PAUSED",
                        "description": "Initial campaign status. Options: ACTIVE, PAUSED, DRAFT. Default: PAUSED (campaign created paused for review before spending money).",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional total lifetime budget. If set, campaign will stop after spending this amount.",
                        "title": "Total Budget"
                      },
                      "years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of years of experience URNs. Example: ['urn:li:yearsOfExperience:3']. Range: 1-12+ years. Up to 2 URNs for lower/upper limit.",
                        "title": "Years Of Experience"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "daily_budget",
                      "organization_id",
                      "introductory_text",
                      "headline",
                      "landing_page_url",
                      "locations"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_linkedin_image_campaign)"
                  },
                  "success": true,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_linkedin_image_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\u26a0\ufe0f STOP - DO NOT CALL THIS TOOL DIRECTLY!\n\nThis tool creates REAL LinkedIn campaigns that cost REAL money [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_linkedin_image_campaign"
      }
    },
    "/api/v1/tools/create_linkedin_text_campaign/execute": {
      "post": {
        "description": "**USE THIS TOOL WHEN:** User wants to create a LinkedIn TEXT ad campaign (desktop right rail/top banner).\n\n**DO NOT USE for image ads** \u2192 use `create_linkedin_image_campaign` instead.\n**DO NOT USE for video ads** \u2192 use `create_linkedin_video_campaign` instead.\n**DO NOT USE for carousel ads** \u2192 use `create_linkedin_carousel_campaign` instead.\n\n**REQUIRED:** Either `campaign_group_name` (creates new group) or `campaign_group_id` (adds to existing group).\n\nIMPORTANT: This creates the campaign with 1 creative (Variation 1). Campaign is created in PAUSED status.\n\nText Ad Specifications:\n- Headline: max 25 characters (required)\n- Description: max 75 characters (required)\n- Image: 100x100 pixels (optional)\n- Landing page URL: HTTPS (required)\n- Desktop-only placement (right rail, top banner)\n\nRequired Parameters:\n- campaign_name, daily_budget, organization_id\n- headline (max 25 chars), description (max 75 chars)\n- landing_page_url, locations (targeting)\n- campaign_group_name OR campaign_group_id\n\nOptional: image_urn or image_url (100x100 image)\n\nDefault Objective: WEBSITE_VISIT\nNote: VIDEO_VIEW and LEAD_GENERATION objectives are NOT supported for text ads.\n\nAD POLICY: NEVER use \"LinkedIn\" in ad copy. NEVER mention competitor platforms.\n\n**After Creation \u2014 IMPORTANT:**\n- This tool created 1 campaign + 1 creative. Campaign Group ID is returned.\n- To add MORE text ad creatives (A/B test headlines): use `add_linkedin_text_creative` with the campaign_id\n- To add MORE campaigns (different audience): use `add_linkedin_campaign_to_group` with the campaign_group_id\n- NEVER call this create tool again without campaign_group_id \u2014 that creates a SEPARATE campaign group\n\nExecution time: 10-20 seconds",
        "operationId": "execute_create_linkedin_text_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_group_id": "string",
                  "campaign_group_name": "string",
                  "campaign_name": "string",
                  "daily_budget": 10.0,
                  "description": "string",
                  "headline": "string",
                  "image_url": "string",
                  "image_urn": "string",
                  "industries": [
                    "string"
                  ],
                  "landing_page_url": "https://example.com",
                  "locations": [
                    "string"
                  ],
                  "objective": "WEBSITE_VISIT",
                  "organization_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating LinkedIn Text Ad campaigns",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of age range URNs.",
                        "title": "Age Ranges"
                      },
                      "buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of buyer group URNs (API 2026-03+).",
                        "title": "Buyer Groups"
                      },
                      "campaign_group_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing Campaign Group ID to add this campaign to. If provided, campaign_group_name is NOT needed.",
                        "title": "Campaign Group Id"
                      },
                      "campaign_group_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). Example: 'Q2 Text Ads'. Max 100 characters.",
                        "title": "Campaign Group Name"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of company size URNs.",
                        "title": "Company Sizes"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for the first creative. E.g. 'B2B Leads - Text Ad 1'. Defaults to '{campaign_name} - Ad 1'.",
                        "title": "Creative Name"
                      },
                      "currency": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "USD",
                        "description": "Currency code. Default: USD.",
                        "title": "Currency"
                      },
                      "daily_budget": {
                        "description": "Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD.",
                        "minimum": 10.0,
                        "title": "Daily Budget",
                        "type": "number"
                      },
                      "degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of degree URNs.",
                        "title": "Degrees"
                      },
                      "description": {
                        "description": "Text ad description (max 75 characters, required).",
                        "maxLength": 75,
                        "title": "Description",
                        "type": "string"
                      },
                      "employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of employer URNs.",
                        "title": "Employers"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional end date in ISO format.",
                        "title": "End Date"
                      },
                      "fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of field of study URNs.",
                        "title": "Fields Of Study"
                      },
                      "followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of followed company URNs.",
                        "title": "Followed Companies"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of gender URNs.",
                        "title": "Genders"
                      },
                      "headline": {
                        "description": "Text ad headline (max 25 characters, required).",
                        "maxLength": 25,
                        "title": "Headline",
                        "type": "string"
                      },
                      "image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional public URL to download and upload 100x100 logo/image for the text ad.",
                        "title": "Image Url"
                      },
                      "image_urn": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Image Urn"
                      },
                      "industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of industry URNs.",
                        "title": "Industries"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of interest URNs.",
                        "title": "Interests"
                      },
                      "job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job function URNs.",
                        "title": "Job Functions"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job title URNs.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Destination URL. Must be HTTPS.",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "locale_country": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "US",
                        "description": "Campaign locale country (ISO-3166). Default: US.",
                        "title": "Locale Country"
                      },
                      "locale_language": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "en",
                        "description": "Campaign locale language (ISO-639). Default: en.",
                        "title": "Locale Language"
                      },
                      "locations": {
                        "description": "List of LinkedIn location URNs to target. Required.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Locations",
                        "type": "array"
                      },
                      "member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of member behavior URNs.",
                        "title": "Member Behaviors"
                      },
                      "member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of LinkedIn group URNs.",
                        "title": "Member Groups"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "WEBSITE_VISIT",
                        "description": "Campaign objective. Default: WEBSITE_VISIT. Options: BRAND_AWARENESS, WEBSITE_VISIT, WEBSITE_CONVERSION.",
                        "title": "Objective"
                      },
                      "organization_id": {
                        "description": "LinkedIn Organization (Company Page) ID.",
                        "title": "Organization Id",
                        "type": "string"
                      },
                      "schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of school URNs.",
                        "title": "Schools"
                      },
                      "seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of seniority level URNs.",
                        "title": "Seniorities"
                      },
                      "skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of skill URNs.",
                        "title": "Skills"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional start date in ISO format.",
                        "title": "Start Date"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "PAUSED",
                        "description": "Initial campaign status. Default: PAUSED.",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional total lifetime budget.",
                        "title": "Total Budget"
                      },
                      "years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of years of experience URNs.",
                        "title": "Years Of Experience"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "daily_budget",
                      "organization_id",
                      "headline",
                      "description",
                      "landing_page_url",
                      "locations"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_linkedin_text_campaign)"
                  },
                  "success": true,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_linkedin_text_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_text_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to create a LinkedIn TEXT ad campaign (desktop right rail/top banner) [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_linkedin_text_campaign"
      }
    },
    "/api/v1/tools/create_linkedin_video_campaign/execute": {
      "post": {
        "description": "**USE THIS TOOL WHEN:** User wants to create a LinkedIn VIDEO ad campaign.\n\n**DO NOT USE for image ads** \u2192 use `create_linkedin_image_campaign` instead.\n**DO NOT USE for carousel ads** \u2192 use `create_linkedin_carousel_campaign` instead.\n**DO NOT USE for text ads** \u2192 use `create_linkedin_text_campaign` instead.\n\n**REQUIRED:** Either `campaign_group_name` (creates new group) or `campaign_group_id` (adds to existing group).\n\nIMPORTANT: This creates the campaign with 1 creative (Variation 1). Campaign is created in PAUSED status.\n\nVideo Specifications:\n- Format: MP4 (H.264 codec)\n- File size: 75KB - 500MB\n- Duration: 3 seconds - 30 minutes (recommended: 15-30 seconds)\n- Aspect ratios: 16:9, 1:1, 4:5, 9:16\n\nVideo Source (one required):\n- video_urn: Existing video URN from discover_linkedin_assets\n- video_url: Public URL to download and upload MP4 video\n\nRequired Parameters:\n- campaign_name, daily_budget, organization_id, headline\n- introductory_text (up to 600 chars), landing_page_url\n- locations (targeting), video source (urn or url)\n- campaign_group_name OR campaign_group_id\n\nDefault Objective: VIDEO_VIEW (optimizes for video views)\n\nAD POLICY: NEVER use \"LinkedIn\" in ad copy. NEVER mention competitor platforms.\n\n**After Creation \u2014 IMPORTANT:**\n- This tool created 1 campaign + 1 creative. Campaign Group ID is returned.\n- To add MORE creatives (A/B test copy): use `add_linkedin_video_creative` with the campaign_id\n- To add MORE campaigns (different audience): use `add_linkedin_campaign_to_group` with the campaign_group_id\n- NEVER call this create tool again without campaign_group_id \u2014 that creates a SEPARATE campaign group\n\nExecution time: 30-120 seconds (includes video upload and processing)",
        "operationId": "execute_create_linkedin_video_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "call_to_action": "LEARN_MORE",
                  "campaign_group_id": "string",
                  "campaign_group_name": "string",
                  "campaign_name": "string",
                  "daily_budget": 10.0,
                  "headline": "string",
                  "introductory_text": "string",
                  "landing_page_url": "https://example.com",
                  "locations": [
                    "string"
                  ],
                  "objective": "VIDEO_VIEW",
                  "organization_id": "string",
                  "video_url": "string",
                  "video_urn": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating LinkedIn Video Ad campaigns",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of age range URNs.",
                        "title": "Age Ranges"
                      },
                      "buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of buyer group URNs (API 2026-03+).",
                        "title": "Buyer Groups"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "Call-to-action button label. Options: APPLY, DOWNLOAD, VIEW_QUOTE, LEARN_MORE, SIGN_UP,SUBSCRIBE, REGISTER, JOIN, ATTEND, REQUEST_DEMO.",
                        "title": "Call To Action"
                      },
                      "campaign_group_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Campaign Group Id"
                      },
                      "campaign_group_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). Example: 'Q2 Video Ads Campaign'. Max 100 characters.",
                        "title": "Campaign Group Name"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of company size URNs.",
                        "title": "Company Sizes"
                      },
                      "creative_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for the first creative. E.g. 'Product Demo - Ad 1'. Defaults to '{campaign_name} - Ad 1'.",
                        "title": "Creative Name"
                      },
                      "currency": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "USD",
                        "description": "Currency code. Default: USD.",
                        "title": "Currency"
                      },
                      "daily_budget": {
                        "description": "Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD.",
                        "minimum": 10.0,
                        "title": "Daily Budget",
                        "type": "number"
                      },
                      "degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of degree URNs.",
                        "title": "Degrees"
                      },
                      "employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of employer (organization) URNs.",
                        "title": "Employers"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional end date in ISO format.",
                        "title": "End Date"
                      },
                      "fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of field of study URNs.",
                        "title": "Fields Of Study"
                      },
                      "followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of followed company URNs.",
                        "title": "Followed Companies"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of gender URNs.",
                        "title": "Genders"
                      },
                      "headline": {
                        "description": "Ad headline (up to 70 characters, REQUIRED). Ads without headlines perform significantly worse.",
                        "maxLength": 70,
                        "title": "Headline",
                        "type": "string"
                      },
                      "industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of industry URNs.",
                        "title": "Industries"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of interest URNs.",
                        "title": "Interests"
                      },
                      "introductory_text": {
                        "description": "Main ad text/copy (up to 600 characters).",
                        "maxLength": 600,
                        "title": "Introductory Text",
                        "type": "string"
                      },
                      "job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job function URNs.",
                        "title": "Job Functions"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of job title URNs.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Destination URL where users will be directed. Must be HTTPS.",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "locale_country": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "US",
                        "description": "Campaign locale country (ISO-3166). Default: US.",
                        "title": "Locale Country"
                      },
                      "locale_language": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "en",
                        "description": "Campaign locale language (ISO-639). Default: en.",
                        "title": "Locale Language"
                      },
                      "locations": {
                        "description": "List of LinkedIn location URNs to target. Required. Example: ['urn:li:geo:103644278'] for United States.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Locations",
                        "type": "array"
                      },
                      "member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of member behavior URNs.",
                        "title": "Member Behaviors"
                      },
                      "member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of LinkedIn group URNs.",
                        "title": "Member Groups"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "VIDEO_VIEW",
                        "description": "Campaign objective. Default: VIDEO_VIEW for video campaigns. Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION, VIDEO_VIEW, JOB_APPLICANT.",
                        "title": "Objective"
                      },
                      "organization_id": {
                        "description": "LinkedIn Organization (Company Page) ID. The organization that will appear as the ad author.",
                        "title": "Organization Id",
                        "type": "string"
                      },
                      "schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of school URNs.",
                        "title": "Schools"
                      },
                      "seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of seniority level URNs.",
                        "title": "Seniorities"
                      },
                      "skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of skill URNs.",
                        "title": "Skills"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional start date in ISO format.",
                        "title": "Start Date"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "PAUSED",
                        "description": "Initial campaign status. Default: PAUSED.",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional total lifetime budget.",
                        "title": "Total Budget"
                      },
                      "video_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Video Url"
                      },
                      "video_urn": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing LinkedIn video URN from discover_linkedin_assets tool. Format: urn:li:video:XXX. Mutually exclusive with video_url.",
                        "title": "Video Urn"
                      },
                      "years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of years of experience URNs.",
                        "title": "Years Of Experience"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "daily_budget",
                      "organization_id",
                      "introductory_text",
                      "headline",
                      "landing_page_url",
                      "locations"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_linkedin_video_campaign)"
                  },
                  "success": true,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_linkedin_video_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_linkedin_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL WHEN:** User wants to create a LinkedIn VIDEO ad campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_linkedin_video_campaign"
      }
    },
    "/api/v1/tools/create_meta_carousel_campaign/execute": {
      "post": {
        "description": "User wants to create a Meta (Facebook/Instagram) carousel ad campaign with multiple images.\n\nREQUIRED: 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.\n\nIMPORTANT: This tool creates REAL campaigns that will spend money once activated. Campaign is created in PAUSED status for review.\n\nDO NOT USE for single image - use `create_meta_image_campaign` instead.\nDO NOT USE for video - use `create_meta_video_campaign` instead.\n\nThis tool creates a complete Meta carousel campaign with:\n1. Campaign (objective, budget)\n2. Ad Set (targeting, placements, schedule)\n3. Multiple Image Uploads (one per card)\n4. Ad Creative (carousel with child_attachments)\n5. Ad (linking creative to ad set)\n\nWhen to use this tool:\n- \"Create a Facebook carousel ad\"\n- \"Launch an Instagram carousel campaign\"\n- \"Create a multi-product ad\"\n- \"Set up an ad with multiple images\"\n\nRequired Parameters:\n- campaign_name: Name for the campaign\n- budget_daily: Daily budget in USD (min $1, recommend $5-20 for testing)\n- primary_text: Main ad text (recommended 125 chars for optimal display, longer text shows with \"See More\")\n- cards: Array of 2-10 cards (see card structure below)\n\nCard Structure (each card requires):\n- image_url OR image_hash: Image source (one required)\n- landing_page_url: Where users go when clicking this card\n- headline: Card headline (max 45 chars)\n- description: Optional card description (max 20 chars)\n- call_to_action: Optional per-card CTA override\n\nOptional Parameters:\n- facebook_page_id: Auto-detected from connected account\n- instagram_account_id: Enable Instagram placements\n- call_to_action: Default CTA for all cards (LEARN_MORE by default)\n- objective: OUTCOME_TRAFFIC (default), OUTCOME_SALES, OUTCOME_LEADS\n- locations: Country codes (default: ['US'])\n- age_min/age_max: Age targeting (18-65)\n- genders: ['male'], ['female'], or null for all\n- multi_share_optimized: Let Meta optimize card order (default: true)\n- pixel_id: Meta Pixel ID for conversion tracking (required for OUTCOME_SALES)\n- pixel_event_name: Conversion event (PURCHASE, LEAD, etc.)\n\nAfter Creation \u2014 IMPORTANT:\n- This tool created 1 campaign + 1 ad set + 1 ad.\n- To add MORE ad sets (different targeting, audiences, or formats), use `add_meta_ad_set` with the returned campaign_id\n- To add MORE ads to the same ad set (A/B test copy/creative), use `add_meta_ad` with the returned ad_set_id\n- NEVER call this create tool again for the same campaign \u2014 that creates a SEPARATE campaign",
        "operationId": "execute_create_meta_carousel_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "budget_daily": 1.0,
                  "budget_lifetime": 1.0,
                  "call_to_action": "LEARN_MORE",
                  "campaign_name": "string",
                  "cards": [
                    {
                      "call_to_action": "SHOP_NOW",
                      "description": "Up to 40% off",
                      "headline": "Spring Sale",
                      "image_url": "https://example.com/card1.jpg",
                      "landing_page_url": "https://example.com/product/1"
                    },
                    {
                      "headline": "New Arrivals",
                      "image_url": "https://example.com/card2.jpg",
                      "landing_page_url": "https://example.com/product/2"
                    }
                  ],
                  "end_time": "string",
                  "facebook_page_id": "string",
                  "objective": "OUTCOME_TRAFFIC",
                  "primary_text": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating Meta carousel ad campaigns",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "age_max": {
                        "default": 65,
                        "description": "Maximum age (18-65). Default: 65",
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Max",
                        "type": "integer"
                      },
                      "age_min": {
                        "default": 18,
                        "description": "Minimum age (18-65). Default: 18",
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Min",
                        "type": "integer"
                      },
                      "behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Behaviors"
                      },
                      "budget_daily": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Daily budget in account currency (minimum $1/day, recommended $5-20/day for testing). Mutually exclusive with budget_lifetime \u2014 provide exactly one.",
                        "title": "Budget Daily"
                      },
                      "budget_lifetime": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lifetime budget in account currency (total spend over campaign duration). REQUIRES end_time to be set. Mutually exclusive with budget_daily \u2014 provide exactly one.",
                        "title": "Budget Lifetime"
                      },
                      "call_to_action": {
                        "default": "LEARN_MORE",
                        "description": "Default call-to-action button for cards without specific CTA. Options: 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'BOOK_TRAVEL'. Default: LEARN_MORE",
                        "title": "Call To Action",
                        "type": "string"
                      },
                      "campaign_budget_optimization": {
                        "default": false,
                        "description": "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.",
                        "title": "Campaign Budget Optimization",
                        "type": "boolean"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "cards": {
                        "description": "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.",
                        "items": {
                          "additionalProperties": true,
                          "type": "object"
                        },
                        "title": "Cards",
                        "type": "array"
                      },
                      "custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of Custom Audience IDs to include in targeting. These are audiences created in Meta Ads Manager (website visitors, customer lists, etc.)",
                        "title": "Custom Audiences"
                      },
                      "custom_conversion_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored \u2014 Meta infers the event type from the custom conversion. Get available custom conversions from your Meta Events Manager.",
                        "title": "Custom Conversion Id"
                      },
                      "daily_min_spend_target": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Min Spend Target"
                      },
                      "daily_spend_cap": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Spend Cap"
                      },
                      "destination_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 using Meta's built-in lead form. Options: WEBSITE, ON_AD, WEBSITE_AND_ON_AD. Only provide when overriding the default.",
                        "title": "Destination Type"
                      },
                      "education_majors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Education Majors"
                      },
                      "education_schools": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Education Schools"
                      },
                      "end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign/ad set end date in ISO format (e.g. '2026-04-30T23:59:59'). REQUIRED when using budget_lifetime. Optional with budget_daily.",
                        "title": "End Time"
                      },
                      "excluded_custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of Custom Audience IDs to exclude from targeting.",
                        "title": "Excluded Custom Audiences"
                      },
                      "facebook_page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Facebook Page Id"
                      },
                      "facebook_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook placement positions. Options: 'feed', 'right_hand_column', 'marketplace', 'video_feeds', 'story', 'search', 'instream_video', 'facebook_reels'. Only include positions you WANT \u2014 omitted positions are excluded.",
                        "title": "Facebook Positions"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Gender targeting: ['male'], ['female'], or None for all genders",
                        "title": "Genders"
                      },
                      "instagram_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram account ID for Instagram placements. If not provided, ad will run on Facebook only.",
                        "title": "Instagram Account Id"
                      },
                      "instagram_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram placement positions. Options: 'stream' (feed), 'story', 'reels', 'explore', 'explore_home', 'ig_search', 'profile_feed'. Only include positions you WANT \u2014 omitted positions are excluded.",
                        "title": "Instagram Positions"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Interests"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Job Titles"
                      },
                      "lead_form_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Lead Form Id"
                      },
                      "life_events": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Life Events"
                      },
                      "locations": {
                        "anyOf": [
                          {
                            "items": {},
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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_targeting with search_type='location'. Default: ['US']",
                        "title": "Locations"
                      },
                      "multi_advertiser": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Multi Advertiser"
                      },
                      "multi_share_optimized": {
                        "default": true,
                        "description": "Whether to let Meta optimize card order based on performance. Default: true (recommended).",
                        "title": "Multi Share Optimized",
                        "type": "boolean"
                      },
                      "objective": {
                        "default": "OUTCOME_TRAFFIC",
                        "description": "Campaign objective. Options: 'OUTCOME_TRAFFIC' (website clicks), 'OUTCOME_SALES' (conversions), 'OUTCOME_LEADS' (lead forms), 'OUTCOME_AWARENESS' (reach), 'OUTCOME_ENGAGEMENT'. Default: OUTCOME_TRAFFIC",
                        "title": "Objective",
                        "type": "string"
                      },
                      "pixel_event_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 events. Default: PURCHASE",
                        "title": "Pixel Event Name"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Pixel ID for conversion tracking. Required for OUTCOME_SALES campaigns. Use list_meta_pixels to find available pixels for your ad account.",
                        "title": "Pixel Id"
                      },
                      "primary_text": {
                        "description": "Primary ad text (max 2200 characters, recommended 125 or less for optimal display). Supports emojis, line breaks (\\n), and bullet points (e.g. '\ud83d\udd25 Limited Offer!\\n\\n\u2705 Free Shipping\\n\u2705 30-Day Returns'). This is the main message that appears above the carousel.",
                        "maxLength": 2200,
                        "title": "Primary Text",
                        "type": "string"
                      },
                      "publisher_platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Platforms to show ads on. Options: 'facebook', 'instagram', 'audience_network', 'messenger'. Default: automatic (all). To exclude Audience Network/Apps, use ['facebook', 'instagram'].",
                        "title": "Publisher Platforms"
                      },
                      "special_ad_categories": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Special ad categories for housing, credit, employment, or social issues. Options: 'HOUSING', 'CREDIT', 'EMPLOYMENT', 'ISSUES_ELECTIONS_POLITICS'. Leave empty for standard ads.",
                        "title": "Special Ad Categories"
                      },
                      "work_employers": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Work Employers"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "primary_text",
                      "cards"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_meta_carousel_campaign)"
                  },
                  "success": true,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_meta_carousel_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_carousel_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a Meta (Facebook/Instagram) carousel ad campaign with multiple images [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_meta_carousel_campaign"
      }
    },
    "/api/v1/tools/create_meta_dco_ad/execute": {
      "post": {
        "description": "User wants Meta to automatically TEST MULTIPLE IMAGES and find the best combination.\n\nThis is Dynamic Creative Optimization (DCO). Meta tests all combinations of images \u00d7 headlines \u00d7 primary texts and optimizes delivery automatically.\n\nUSE THIS TOOL when the user says ANY of these:\n- \"test these images\" / \"test multiple images\"\n- \"let Meta find the winner\" / \"let Meta optimize\"\n- \"Dynamic Creative\" / \"DCO\"\n- \"which image performs best\"\n- Provides multiple images and wants Meta to choose the best\n- \"test combinations\" / \"mix and match\"\n- \"10 images \u00d7 5 headlines\"\n\nDO NOT USE add_meta_ad multiple times for this \u2014 that creates separate ads (A/B test).\nDCO is ONE ad with multiple assets that Meta mixes and optimizes.\n\nPrerequisites:\n1. You need an ad_set_id with is_dynamic_creative=true\n2. If the user doesn't have one, first call `add_meta_ad_set` with `is_dynamic_creative=true`\n3. Then call this tool with the image_urls and text variations\n\nAsset limits (from Meta):\n- Images: 2-10 (required)\n- Headlines: up to 5 (optional)\n- Primary texts: up to 5 (optional)\n- Descriptions: up to 5 (optional)\n- Total assets: max 30\n\nDCO ad sets support only 1 ad \u2014 do NOT add more ads to a DCO ad set.\n\nExample: 5 images \u00d7 3 headlines \u00d7 2 primary texts = 30 combinations, Meta finds the winner.",
        "operationId": "execute_create_meta_dco_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_set_id": "string",
                  "call_to_action": "LEARN_MORE",
                  "description": "string",
                  "descriptions": [
                    "string"
                  ],
                  "headline": "string",
                  "headlines": [
                    "string"
                  ],
                  "image_urls": [
                    "string"
                  ],
                  "landing_page_url": "https://example.com",
                  "primary_text": "string",
                  "primary_texts": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating a Dynamic Creative Optimization (DCO) ad.\n\nDCO lets Meta test combinations of multiple images, headlines, primary texts,\nand descriptions to find the best-performing mix automatically.",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta ad account ID",
                        "title": "Ad Account Id"
                      },
                      "ad_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom name for the DCO ad",
                        "title": "Ad Name"
                      },
                      "ad_set_id": {
                        "description": "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).",
                        "title": "Ad Set Id",
                        "type": "string"
                      },
                      "call_to_action": {
                        "default": "LEARN_MORE",
                        "description": "CTA button",
                        "title": "Call To Action",
                        "type": "string"
                      },
                      "description": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single description (used if descriptions is not provided).",
                        "title": "Description"
                      },
                      "descriptions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of description variations (up to 5) for Meta to test.",
                        "title": "Descriptions"
                      },
                      "facebook_page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook Page ID",
                        "title": "Facebook Page Id"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single headline (used if headlines is not provided).",
                        "title": "Headline"
                      },
                      "headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of headline variations (up to 5) for Meta to test.",
                        "title": "Headlines"
                      },
                      "image_urls": {
                        "description": "List of 2-10 image URLs for Meta to test. Meta will try all combinations.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Image Urls",
                        "type": "array"
                      },
                      "instagram_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram account ID",
                        "title": "Instagram Account Id"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL (HTTPS)",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "lead_form_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lead form ID for OUTCOME_LEADS campaigns.",
                        "title": "Lead Form Id"
                      },
                      "primary_text": {
                        "description": "Default primary text (used if primary_texts is not provided).",
                        "maxLength": 2200,
                        "title": "Primary Text",
                        "type": "string"
                      },
                      "primary_texts": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of primary text variations (up to 5) for Meta to test.",
                        "title": "Primary Texts"
                      }
                    },
                    "required": [
                      "ad_set_id",
                      "image_urls",
                      "primary_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_meta_dco_ad)"
                  },
                  "success": true,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_meta_dco_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_dco_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants Meta to automatically TEST MULTIPLE IMAGES and find the best combination [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_meta_dco_ad"
      }
    },
    "/api/v1/tools/create_meta_image_campaign/execute": {
      "post": {
        "description": "User wants to create a Meta (Facebook/Instagram) single-image ad campaign.\n\nREQUIRED: 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.\n\nIMPORTANT: This tool creates REAL campaigns that will spend money once activated. Campaign is created in PAUSED status for review.\n\nDO NOT USE for video ads - use `create_meta_video_campaign` instead.\nDO NOT USE for carousel/multi-image - use `create_meta_carousel_campaign` instead.\n\nThis tool creates a complete Meta campaign with:\n1. Campaign (objective, budget)\n2. Ad Set (targeting, placements, schedule)\n3. Ad Creative (image, text, CTA)\n4. Ad (linking creative to ad set)\n\nWhen to use this tool:\n- \"Create a Facebook ad campaign\"\n- \"Launch an Instagram image ad\"\n- \"Set up a Meta traffic campaign\"\n- \"Create an ad with this image\"\n\nRequired Parameters:\n- campaign_name: Name for the campaign\n- budget_daily: Daily budget in USD (min $1, recommend $5-20 for testing)\n- primary_text: Main ad text (recommended 125 chars for optimal display, longer text shows with \"See More\")\n- headline: Headline below image (max 255 chars, recommended 40)\n- landing_page_url: Where users go when clicking\n\nImage Source (choose ONE):\n- asset_bundle_id: From `validate_and_prepare_meta_assets` (recommended for new images)\n- existing_image_hash: From `discover_meta_assets` (for reusing existing images)\n- image_url: Direct URL (uploaded during creation - use validate_and_prepare for better error handling)\n\nOptional Parameters:\n- facebook_page_id: Auto-detected from connected account. Only provide if multiple pages.\n- instagram_account_id: Auto-detected if linked. Enable Instagram placements.\n- objective: OUTCOME_TRAFFIC (default), OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS\n- call_to_action: LEARN_MORE (default), SHOP_NOW, SIGN_UP, etc.\n- locations: Country codes (default: ['US']) or location objects from search_meta_targeting\n- age_min/age_max: Age targeting (18-65)\n- genders: ['male'], ['female'], or null for all\n- pixel_id: Meta Pixel ID for conversion tracking (required for OUTCOME_SALES)\n- pixel_event_name: Conversion event (PURCHASE, LEAD, etc.)\n- story_image_url: Different image for Stories/Reels (9:16, 1080x1920px). Uses asset_feed_spec.\n- right_column_image_url: Different image for Right Column (1.91:1, 1200x628px). Uses asset_feed_spec.\n\nMulti-Placement Creatives:\nIf the user wants different images for different placements (Feed, Stories, Right Column),\nprovide story_image_url and/or right_column_image_url. The main image will be used for Feed.\nThis uses Meta's asset_feed_spec with asset_customization_rules instead of object_story_spec.\n\nAfter Creation \u2014 IMPORTANT:\n- This tool created 1 campaign + 1 ad set + 1 ad.\n- To add MORE ad sets (different targeting, audiences, or formats), use `add_meta_ad_set` with the returned campaign_id\n- To add MORE ads to the same ad set (A/B test copy/creative), use `add_meta_ad` with the returned ad_set_id\n- NEVER call this create tool again for the same campaign \u2014 that creates a SEPARATE campaign",
        "operationId": "execute_create_meta_image_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "asset_bundle_id": "string",
                  "budget_daily": 1.0,
                  "budget_lifetime": 1.0,
                  "campaign_name": "string",
                  "end_time": "string",
                  "existing_image_hash": "string",
                  "headline": "string",
                  "landing_page_url": "https://example.com",
                  "objective": "OUTCOME_TRAFFIC",
                  "primary_text": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating Meta single-image ad campaigns",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "age_max": {
                        "default": 65,
                        "description": "Maximum age (18-65). Default: 65",
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Max",
                        "type": "integer"
                      },
                      "age_min": {
                        "default": 18,
                        "description": "Minimum age (18-65). Default: 18",
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Min",
                        "type": "integer"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Asset Bundle Id"
                      },
                      "behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Behaviors"
                      },
                      "budget_daily": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Daily budget in account currency (minimum $1/day, recommended $5-20/day for testing). Mutually exclusive with budget_lifetime \u2014 provide exactly one.",
                        "title": "Budget Daily"
                      },
                      "budget_lifetime": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lifetime budget in account currency (total spend over campaign duration). REQUIRES end_time to be set. Mutually exclusive with budget_daily \u2014 provide exactly one.",
                        "title": "Budget Lifetime"
                      },
                      "call_to_action": {
                        "default": "LEARN_MORE",
                        "description": "Call-to-action button. Options: 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'BOOK_TRAVEL', 'CONTACT_US', 'GET_QUOTE', 'SUBSCRIBE', 'WATCH_MORE'. Default: LEARN_MORE",
                        "title": "Call To Action",
                        "type": "string"
                      },
                      "campaign_budget_optimization": {
                        "default": false,
                        "description": "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.",
                        "title": "Campaign Budget Optimization",
                        "type": "boolean"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of Custom Audience IDs to include in targeting. These are audiences created in Meta Ads Manager (website visitors, customer lists, etc.)",
                        "title": "Custom Audiences"
                      },
                      "custom_conversion_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored \u2014 Meta infers the event type from the custom conversion. Get available custom conversions from your Meta Events Manager.",
                        "title": "Custom Conversion Id"
                      },
                      "daily_min_spend_target": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Min Spend Target"
                      },
                      "daily_spend_cap": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Spend Cap"
                      },
                      "description": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional description text (max 255 characters, recommended 30). Appears under the headline on some placements.",
                        "title": "Description"
                      },
                      "destination_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 using Meta's built-in lead form. Options: WEBSITE, ON_AD, WEBSITE_AND_ON_AD. Only provide when overriding the default.",
                        "title": "Destination Type"
                      },
                      "education_majors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Education Majors"
                      },
                      "education_schools": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Education Schools"
                      },
                      "end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign/ad set end date in ISO format (e.g. '2026-04-30T23:59:59'). REQUIRED when using budget_lifetime. Optional with budget_daily.",
                        "title": "End Time"
                      },
                      "excluded_custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of Custom Audience IDs to exclude from targeting.",
                        "title": "Excluded Custom Audiences"
                      },
                      "existing_image_hash": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Existing Image Hash"
                      },
                      "facebook_page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Facebook Page Id"
                      },
                      "facebook_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook placement positions. Options: 'feed', 'right_hand_column', 'marketplace', 'video_feeds', 'story', 'search', 'instream_video', 'facebook_reels'. Only include positions you WANT \u2014 omitted positions are excluded.",
                        "title": "Facebook Positions"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Gender targeting: ['male'], ['female'], or None for all genders",
                        "title": "Genders"
                      },
                      "headline": {
                        "description": "Headline text (max 255 characters, recommended 40). Appears below the image as clickable text.",
                        "maxLength": 255,
                        "title": "Headline",
                        "type": "string"
                      },
                      "image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Image Url"
                      },
                      "instagram_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram account ID for Instagram placements. If not provided, ad will run on Facebook only.",
                        "title": "Instagram Account Id"
                      },
                      "instagram_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram placement positions. Options: 'stream' (feed), 'story', 'reels', 'explore', 'explore_home', 'ig_search', 'profile_feed'. Only include positions you WANT \u2014 omitted positions are excluded.",
                        "title": "Instagram Positions"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Interests"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL where users will be directed when clicking the ad. Must be HTTPS.",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "lead_form_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Lead Form Id"
                      },
                      "life_events": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Life Events"
                      },
                      "locations": {
                        "anyOf": [
                          {
                            "items": {},
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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_targeting with search_type='location'. Default: ['US']",
                        "title": "Locations"
                      },
                      "multi_advertiser": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Multi Advertiser"
                      },
                      "objective": {
                        "default": "OUTCOME_TRAFFIC",
                        "description": "Campaign objective. Options: 'OUTCOME_TRAFFIC' (website clicks), 'OUTCOME_SALES' (conversions), 'OUTCOME_LEADS' (lead forms), 'OUTCOME_AWARENESS' (reach), 'OUTCOME_ENGAGEMENT'. Default: OUTCOME_TRAFFIC",
                        "title": "Objective",
                        "type": "string"
                      },
                      "pixel_event_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 events. Default: PURCHASE",
                        "title": "Pixel Event Name"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Pixel ID for conversion tracking. Required for OUTCOME_SALES campaigns. Use list_meta_pixels to find available pixels for your ad account.",
                        "title": "Pixel Id"
                      },
                      "primary_text": {
                        "description": "Primary ad text (max 2200 characters, recommended 125 or less for optimal display). Supports emojis, line breaks (\\n), and bullet points (e.g. '\ud83d\udd25 Limited Offer!\\n\\n\u2705 Free Shipping\\n\u2705 30-Day Returns'). This is the main message that appears above the image.",
                        "maxLength": 2200,
                        "title": "Primary Text",
                        "type": "string"
                      },
                      "publisher_platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Platforms to show ads on. Options: 'facebook', 'instagram', 'audience_network', 'messenger'. Default: automatic (all). To exclude Audience Network/Apps, use ['facebook', 'instagram'].",
                        "title": "Publisher Platforms"
                      },
                      "right_column_image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Right Column Image Url"
                      },
                      "special_ad_categories": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Special ad categories for housing, credit, employment, or social issues. Options: 'HOUSING', 'CREDIT', 'EMPLOYMENT', 'ISSUES_ELECTIONS_POLITICS'. Leave empty for standard ads.",
                        "title": "Special Ad Categories"
                      },
                      "story_image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Story Image Url"
                      },
                      "work_employers": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Work Employers"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "primary_text",
                      "headline",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_meta_image_campaign)"
                  },
                  "success": true,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_meta_image_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_image_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a Meta (Facebook/Instagram) single-image ad campaign [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_meta_image_campaign"
      }
    },
    "/api/v1/tools/create_meta_video_campaign/execute": {
      "post": {
        "description": "User wants to create a Meta (Facebook/Instagram) video ad campaign.\n\nREQUIRED: 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.\n\nIMPORTANT: This tool creates REAL campaigns that will spend money once activated. Campaign is created in PAUSED status for review.\n\nDO NOT USE for image ads - use `create_meta_image_campaign` instead.\nDO NOT USE for carousel - use `create_meta_carousel_campaign` instead.\n\nThis tool creates a complete Meta video campaign with:\n1. Campaign (objective, budget)\n2. Ad Set (targeting, placements, schedule)\n3. Video Upload (handles large files with chunked upload)\n4. Ad Creative (video, text, CTA)\n5. Ad (linking creative to ad set)\n\nWhen to use this tool:\n- \"Create a Facebook video ad campaign\"\n- \"Launch an Instagram video ad\"\n- \"Set up a Meta Reels campaign\"\n- \"Create an ad with this video\"\n\nRequired Parameters:\n- campaign_name: Name for the campaign\n- budget_daily: Daily budget in USD (min $1, recommend $5-20 for testing)\n- primary_text: Main ad text (recommended 125 chars for optimal display, longer text shows with \"See More\")\n- landing_page_url: Where users go when clicking\n\nVideo Source (choose ONE):\n- video_url: Public video URL (will be uploaded during creation)\n- existing_video_id: Existing Meta video ID (for reusing previously uploaded videos)\n\nOptional Parameters:\n- facebook_page_id: Auto-detected from connected account\n- instagram_account_id: Enable Instagram placements\n- thumbnail_url: Custom thumbnail image (Meta auto-generates if not provided)\n- headline: Headline below video (optional for video ads)\n- description: Description text (max 255 chars, recommended 30)\n- call_to_action: WATCH_MORE (default), LEARN_MORE, SHOP_NOW, etc.\n- objective: OUTCOME_TRAFFIC (default), OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS\n- locations: Country codes (default: ['US'])\n- age_min/age_max: Age targeting (18-65)\n- genders: ['male'], ['female'], or null for all\n- optimize_for_reels: true for vertical (9:16) videos\n- pixel_id: Meta Pixel ID for conversion tracking (required for OUTCOME_SALES)\n- pixel_event_name: Conversion event (PURCHASE, LEAD, etc.)\n\nVideo Specifications:\n- Formats: MP4, MOV (recommended: MP4 H.264)\n- Max size: 4GB (recommended under 1GB)\n- Duration: 1 sec - 240 min (recommended 15-60 sec)\n- Feed: 1:1 or 4:5 aspect ratio\n- Stories/Reels: 9:16 aspect ratio\n\nAfter Creation \u2014 IMPORTANT:\n- This tool created 1 campaign + 1 ad set + 1 ad.\n- To add MORE ad sets (different targeting, audiences, or formats), use `add_meta_ad_set` with the returned campaign_id\n- To add MORE ads to the same ad set (A/B test copy/creative), use `add_meta_ad` with the returned ad_set_id\n- NEVER call this create tool again for the same campaign \u2014 that creates a SEPARATE campaign",
        "operationId": "execute_create_meta_video_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "budget_daily": 1.0,
                  "budget_lifetime": 1.0,
                  "campaign_name": "string",
                  "end_time": "string",
                  "existing_video_id": "string",
                  "landing_page_url": "https://example.com",
                  "objective": "OUTCOME_TRAFFIC",
                  "primary_text": "string",
                  "video_url": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating Meta video ad campaigns",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "age_max": {
                        "default": 65,
                        "description": "Maximum age (18-65). Default: 65",
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Max",
                        "type": "integer"
                      },
                      "age_min": {
                        "default": 18,
                        "description": "Minimum age (18-65). Default: 18",
                        "maximum": 65,
                        "minimum": 18,
                        "title": "Age Min",
                        "type": "integer"
                      },
                      "behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Behaviors"
                      },
                      "budget_daily": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Daily budget in account currency (minimum $1/day, recommended $5-20/day for testing). Mutually exclusive with budget_lifetime \u2014 provide exactly one.",
                        "title": "Budget Daily"
                      },
                      "budget_lifetime": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lifetime budget in account currency (total spend over campaign duration). REQUIRES end_time to be set. Mutually exclusive with budget_daily \u2014 provide exactly one.",
                        "title": "Budget Lifetime"
                      },
                      "call_to_action": {
                        "default": "WATCH_MORE",
                        "description": "Call-to-action button. Options: 'WATCH_MORE' (default for video), 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'BOOK_TRAVEL', 'CONTACT_US'. Default: WATCH_MORE",
                        "title": "Call To Action",
                        "type": "string"
                      },
                      "campaign_budget_optimization": {
                        "default": false,
                        "description": "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.",
                        "title": "Campaign Budget Optimization",
                        "type": "boolean"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of Custom Audience IDs to include in targeting. These are audiences created in Meta Ads Manager (website visitors, customer lists, etc.)",
                        "title": "Custom Audiences"
                      },
                      "custom_conversion_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored \u2014 Meta infers the event type from the custom conversion. Get available custom conversions from your Meta Events Manager.",
                        "title": "Custom Conversion Id"
                      },
                      "daily_min_spend_target": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Min Spend Target"
                      },
                      "daily_spend_cap": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Spend Cap"
                      },
                      "description": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional description text (max 255 characters, recommended 30). Appears under the headline on some placements.",
                        "title": "Description"
                      },
                      "destination_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 using Meta's built-in lead form. Options: WEBSITE, ON_AD, WEBSITE_AND_ON_AD. Only provide when overriding the default.",
                        "title": "Destination Type"
                      },
                      "education_majors": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Education Majors"
                      },
                      "education_schools": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Education Schools"
                      },
                      "end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign/ad set end date in ISO format (e.g. '2026-04-30T23:59:59'). REQUIRED when using budget_lifetime. Optional with budget_daily.",
                        "title": "End Time"
                      },
                      "excluded_custom_audiences": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of Custom Audience IDs to exclude from targeting.",
                        "title": "Excluded Custom Audiences"
                      },
                      "existing_video_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Existing Meta video ID from your Ad Library. Use this to REUSE videos already uploaded to Meta. Mutually exclusive with video_url.",
                        "title": "Existing Video Id"
                      },
                      "facebook_page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Facebook Page Id"
                      },
                      "facebook_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook placement positions. Options: 'feed', 'right_hand_column', 'marketplace', 'video_feeds', 'story', 'search', 'instream_video', 'facebook_reels'. Only include positions you WANT \u2014 omitted positions are excluded.",
                        "title": "Facebook Positions"
                      },
                      "genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Gender targeting: ['male'], ['female'], or None for all genders",
                        "title": "Genders"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 255,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Headline text (max 255 characters, recommended 40). Optional for video ads. Appears below the video on some placements.",
                        "title": "Headline"
                      },
                      "instagram_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram account ID for Instagram placements. If not provided, ad will run on Facebook only.",
                        "title": "Instagram Account Id"
                      },
                      "instagram_positions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Instagram placement positions. Options: 'stream' (feed), 'story', 'reels', 'explore', 'explore_home', 'ig_search', 'profile_feed'. Only include positions you WANT \u2014 omitted positions are excluded.",
                        "title": "Instagram Positions"
                      },
                      "interests": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Interests"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Job Titles"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL where users will be directed when clicking the ad. Must be HTTPS.",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "lead_form_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Lead Form Id"
                      },
                      "life_events": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Life Events"
                      },
                      "locations": {
                        "anyOf": [
                          {
                            "items": {},
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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_targeting with search_type='location'. Default: ['US']",
                        "title": "Locations"
                      },
                      "multi_advertiser": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Multi Advertiser"
                      },
                      "objective": {
                        "default": "OUTCOME_TRAFFIC",
                        "description": "Campaign objective. Options: 'OUTCOME_TRAFFIC' (website clicks), 'OUTCOME_SALES' (conversions), 'OUTCOME_LEADS' (lead forms), 'OUTCOME_AWARENESS' (reach), 'OUTCOME_ENGAGEMENT'. Default: OUTCOME_TRAFFIC",
                        "title": "Objective",
                        "type": "string"
                      },
                      "optimize_for_reels": {
                        "default": false,
                        "description": "Whether to optimize for Reels placement. Set to true for vertical videos (9:16 aspect ratio).",
                        "title": "Optimize For Reels",
                        "type": "boolean"
                      },
                      "pixel_event_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 events. Default: PURCHASE",
                        "title": "Pixel Event Name"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Pixel ID for conversion tracking. Required for OUTCOME_SALES campaigns. Use list_meta_pixels to find available pixels for your ad account.",
                        "title": "Pixel Id"
                      },
                      "primary_text": {
                        "description": "Primary ad text (max 2200 characters, recommended 125 or less for optimal display). Supports emojis, line breaks (\\n), and bullet points (e.g. '\ud83d\udd25 Limited Offer!\\n\\n\u2705 Free Shipping\\n\u2705 30-Day Returns'). This is the main message that appears above the video.",
                        "maxLength": 2200,
                        "title": "Primary Text",
                        "type": "string"
                      },
                      "publisher_platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Platforms to show ads on. Options: 'facebook', 'instagram', 'audience_network', 'messenger'. Default: automatic (all). To exclude Audience Network/Apps, use ['facebook', 'instagram'].",
                        "title": "Publisher Platforms"
                      },
                      "special_ad_categories": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Special ad categories for housing, credit, employment, or social issues. Options: 'HOUSING', 'CREDIT', 'EMPLOYMENT', 'ISSUES_ELECTIONS_POLITICS'. Leave empty for standard ads.",
                        "title": "Special Ad Categories"
                      },
                      "thumbnail_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional custom thumbnail image URL. If not provided, Meta will auto-generate thumbnails from video.",
                        "title": "Thumbnail Url"
                      },
                      "video_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Video Url"
                      },
                      "work_employers": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Work Employers"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "primary_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_meta_video_campaign)"
                  },
                  "success": true,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_meta_video_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_meta_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a Meta (Facebook/Instagram) video ad campaign [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_meta_video_campaign"
      }
    },
    "/api/v1/tools/create_monitor/execute": {
      "post": {
        "description": "Create a monitoring alert for your campaigns. Checked daily.\n\n**Supported metrics:** roas, ctr, cpc, cpa, cpm, cpv, spend, conversions, impressions, clicks, cost_per_lead, conversion_rate, budget_utilization, video_views, engagement_rate\n\n**Operators:** less_than, greater_than, less_than_or_equal, greater_than_or_equal, changes_by\n\n**Advanced features:**\n- **Multiple conditions with AND/OR:** Use `conditions` array with `conditions_logic: \"AND\"` or `\"OR\"`\n- **Consecutive days:** Add `consecutive_days: 3` to only trigger after 3 days in a row\n- **Relative thresholds:** Set `threshold_type: \"relative\"` with `threshold_multiplier: 1.5` to mean \"50% above average\"\n- **% change detection:** Use `operator: \"changes_by\"` with `direction: \"decrease\"` and `threshold: 30` for \"dropped 30%\"\n- **Campaign targeting:** Use `campaign_ids` to monitor specific campaigns only\n- **Auto-actions (coming soon):** `auto_action: \"pause_campaign\"` or `\"increase_budget\"` with `auto_action_value: 20`\n\n**Examples:**\n\n1. Simple: \"Alert me if ROAS drops below 2\"\n\u2192 metric: \"roas\", operator: \"less_than\", threshold: 2.0\n\n2. Consecutive days: \"Alert if CPA exceeds $50 for 3 days straight\"\n\u2192 metric: \"cpa\", operator: \"greater_than\", threshold: 50, consecutive_days: 3\n\n3. Compound AND: \"Alert if CTR < 0.8% AND spend > $200\"\n\u2192 conditions: [\n    {metric: \"ctr\", operator: \"less_than\", threshold: 0.8},\n    {metric: \"spend\", operator: \"greater_than\", threshold: 200}\n  ], conditions_logic: \"AND\"\n\n4. Relative: \"Alert if CPA goes 50% above my 30-day average\"\n\u2192 metric: \"cpa\", operator: \"greater_than\", threshold_type: \"relative\", threshold_multiplier: 1.5, threshold_timeframe: \"last_30d\"\n\n5. % drop: \"Notify me if cost per lead drops 30% in a day\"\n\u2192 metric: \"cost_per_lead\", operator: \"changes_by\", threshold: 30, direction: \"decrease\", timeframe: \"vs_previous_day\"\n\n**IMPORTANT:**\n- If user doesn't specify an email for alerts, ASK them. Do not guess.\n- If user doesn't specify platforms, default to all their connected platforms.\n- If user doesn't specify campaigns, default to all campaigns.\n- Do not assume timeframe \u2014 ask if unclear.\n- Do not assume threshold values \u2014 ask if the user doesn't specify a number.\n- ROAS monitors are skipped on traffic/awareness/engagement campaigns automatically.\n- CPA monitors are skipped on campaigns with zero conversions automatically.",
        "operationId": "execute_create_monitor",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "conditions": [
                    {
                      "consecutive_days": 1,
                      "direction": "string",
                      "metric": "string",
                      "operator": "string",
                      "threshold": 1.0,
                      "threshold_multiplier": 1.0,
                      "threshold_timeframe": "string",
                      "threshold_type": "string",
                      "timeframe": "string"
                    }
                  ],
                  "conditions_logic": "AND",
                  "metric": "string",
                  "monitor_type": "performance_drop",
                  "name": "string",
                  "operator": "string",
                  "threshold": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "$defs": {
                      "MonitorConditionInput": {
                        "description": "A single monitoring condition.",
                        "properties": {
                          "consecutive_days": {
                            "anyOf": [
                              {
                                "type": "integer"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Only trigger if condition is true for this many consecutive days. E.g., 3 means 'true for 3 days in a row'. Min 1, max 30.",
                            "title": "Consecutive Days"
                          },
                          "direction": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "For 'changes_by' operator: 'increase', 'decrease', or 'any'. E.g., 'decrease' with threshold 30 means 'metric dropped by 30%'.",
                            "title": "Direction"
                          },
                          "metric": {
                            "description": "Metric to check: 'roas', 'ctr', 'cpc', 'cpa', 'cpm', 'cpv', 'spend', 'conversions', 'impressions', 'clicks', 'cost_per_lead', 'conversion_rate', 'budget_utilization', 'video_views', 'engagement_rate'",
                            "title": "Metric",
                            "type": "string"
                          },
                          "operator": {
                            "description": "Comparison: 'less_than', 'greater_than', 'less_than_or_equal', 'greater_than_or_equal', 'changes_by'",
                            "title": "Operator",
                            "type": "string"
                          },
                          "threshold": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Threshold value for static comparisons (e.g., 2.0 for ROAS, 50 for CPA). Required unless threshold_type is 'relative'.",
                            "title": "Threshold"
                          },
                          "threshold_multiplier": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "For relative thresholds: multiplier applied to baseline. E.g., 1.5 means '50% above baseline'. Required when threshold_type='relative'.",
                            "title": "Threshold Multiplier"
                          },
                          "threshold_timeframe": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "For relative thresholds: timeframe for the baseline average. E.g., 'last_30d' means compare to 30-day average. Default: 'last_30d'.",
                            "title": "Threshold Timeframe"
                          },
                          "threshold_type": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "'static' (default) = compare to fixed threshold value. 'relative' = compare to baseline metric \u00d7 multiplier (e.g., 'CPA > 30-day average \u00d7 1.5').",
                            "title": "Threshold Type"
                          },
                          "timeframe": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Data window: 'last_24h', 'last_7d', 'last_14d', 'last_30d', 'today', 'yesterday', 'vs_previous_day'. If not specified, uses the lookback period of available data.",
                            "title": "Timeframe"
                          }
                        },
                        "required": [
                          "metric",
                          "operator"
                        ],
                        "title": "MonitorConditionInput",
                        "type": "object"
                      }
                    },
                    "description": "Input for creating a monitoring alert.",
                    "properties": {
                      "alert_destination": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Where to send alerts: email address, Slack webhook URL, etc. If not provided, uses the user's account email.",
                        "title": "Alert Destination"
                      },
                      "alert_method": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "email",
                        "description": "How to notify: 'email', 'slack', 'webhook'. Default: email.",
                        "title": "Alert Method"
                      },
                      "auto_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Automatic action when triggered: 'pause_campaign', 'increase_budget', 'decrease_budget', 'notify_only'. Default: notify_only (no automatic changes).",
                        "title": "Auto Action"
                      },
                      "auto_action_value": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Value for auto-action: percentage for budget changes (e.g., 20 for +20%), ignored for pause/notify.",
                        "title": "Auto Action Value"
                      },
                      "campaign_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Specific campaign IDs to monitor. Default: all campaigns. Get IDs from list_campaigns/list_meta_campaigns tools.",
                        "title": "Campaign Ids"
                      },
                      "check_frequency": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "daily",
                        "description": "How often to check: 'daily' \u2014 checks once per day using data through yesterday. This is the only available frequency currently.",
                        "title": "Check Frequency"
                      },
                      "conditions": {
                        "anyOf": [
                          {
                            "items": {
                              "$ref": "#/$defs/MonitorConditionInput"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Conditions"
                      },
                      "conditions_logic": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "AND",
                        "description": "How multiple conditions combine: 'AND' (all must be true) or 'OR' (any must be true). Default: 'AND'.",
                        "title": "Conditions Logic"
                      },
                      "consecutive_days": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single condition consecutive days (shorthand).",
                        "title": "Consecutive Days"
                      },
                      "metric": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single condition metric (shorthand). Use 'conditions' array for multiple conditions.",
                        "title": "Metric"
                      },
                      "monitor_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "performance_drop",
                        "description": "Type: 'performance_drop', 'budget_pace', 'anomaly_detection', 'spend_threshold', 'conversion_tracking'",
                        "title": "Monitor Type"
                      },
                      "name": {
                        "description": "Name for this alert (e.g., 'High CPA Auto-Pause', 'ROAS Scaling Rule')",
                        "title": "Name",
                        "type": "string"
                      },
                      "operator": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single condition operator (shorthand).",
                        "title": "Operator"
                      },
                      "platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Platforms to monitor: ['google_ads'], ['meta_ads'], ['google_ads', 'meta_ads'], etc. Default: all connected platforms.",
                        "title": "Platforms"
                      },
                      "scope_level": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "campaign",
                        "description": "What level to monitor: 'campaign' (default), 'ad_set' (ad group level), or 'ad' (individual ad/creative level).",
                        "title": "Scope Level"
                      },
                      "threshold": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single condition threshold (shorthand).",
                        "title": "Threshold"
                      },
                      "timeframe": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single condition timeframe (shorthand).",
                        "title": "Timeframe"
                      }
                    },
                    "required": [
                      "name"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_monitor)"
                  },
                  "success": true,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_monitor",
                  "is_error": true,
                  "success": false,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Create a monitoring alert for your campaigns [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_monitor"
      }
    },
    "/api/v1/tools/create_pmax_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\n\ud83d\udd04 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 \u2192 commit.\n\n\u26a0\ufe0f CRITICAL PREREQUISITES:\n1. MUST have valid asset_bundle_id from validate_and_prepare_assets tool OR existing_image_ids from discover_existing_assets\n2. MUST have all campaign text details from user\n3. Creates REAL campaigns that cost REAL money\n4. Call ONLY ONCE per campaign - do NOT retry automatically\n\n\ud83c\udfa5 **VIDEOS ARE OPTIONAL BUT RECOMMENDED:**\n- Videos significantly improve PMAX performance (higher CTR, better engagement)\n- **Always ask the user**: \"Would you like to add YouTube videos to this campaign?\"\n- If yes: validate with `validate_video` tool, then include `youtube_video_ids` parameter\n- If no: proceed without videos (campaigns work fine either way)\n- See STEP 3.5 below for complete video guidance\n\n\u26a0\ufe0f GOOGLE ADS POLICY NOTE:\nAvoid keywords related to health conditions, medical treatments, financial hardship, or political topics. These may trigger policy violations. Use general service terms instead.\nExample: Use \"senior care services\" not \"nursing care\", \"home services\" not \"medical services\"\n\n\ud83d\udccb **YOUR CRITICAL ROLE: Campaign Strategist & Text Creator**\n\n**STEP 1: Verify Prerequisites**\n\nBefore calling this tool, ensure you have:\n- \u2705 Valid asset_bundle_id from validate_and_prepare_assets (max 1 hour old)\n- \u2705 All required campaign details from user (see below)\n\nIf asset_bundle_id is missing or expired:\n- Guide user to re-upload images\n- Call validate_and_prepare_assets again\n- Get new asset_bundle_id\n\n**STEP 2: Collect Campaign Details from User**\n\nYOU MUST collect these from the user (ask if not provided):\n\n**REQUIRED Fields:**\n1. **Campaign Name**\n   - Example: \"Premium Watches Summer 2025\"\n   - Make it descriptive and unique\n\n2. **Daily Budget**\n   - Google recommends ~$50/day USD equivalent for Performance Max\n   - IMPORTANT: Accept the user's budget in their stated currency \u2014 do NOT convert to USD\n   - Ask: \"What's your daily advertising budget?\"\n\n3. **Final URL** (Landing Page)\n   - Must match verified domain in Google Ads\n   - Example: \"https://example.com/watches\"\n   - Ask: \"What's the landing page URL for this campaign?\"\n\n4. **Business Name**\n   - Maximum 25 characters\n   - Example: \"Luxury Watch Co\"\n   - Will appear in ads\n\n**STEP 3: Generate High-Quality Ad Copy**\n\nAs an expert copywriter, you MUST create compelling ad text:\n\n**\ud83d\udea8 BEFORE GENERATING TEXT - READ CHARACTER LIMITS \ud83d\udea8**\n\n**Headlines (3-15 required):**\n- **STRICT LIMIT: 30 characters maximum per headline**\n- Count characters BEFORE calling this tool!\n- Examples:\n  - \"Premium Luxury Watches\" (22 chars) \u2705\n  - \"Free Shipping\" (13 chars) \u2705\n  - \"Certified Authentic\" (19 chars) \u2705\n\n**Descriptions (2-4 required):**\n- **STRICT LIMIT: 80 characters maximum per description**\n- Count characters BEFORE calling this tool!\n- Examples:\n  - \"Shop authentic luxury timepieces with free worldwide shipping.\" (63 chars) \u2705\n  - \"Expert-curated Swiss watches. Certified authentic. Shop now.\" (61 chars) \u2705\n\n**Long Headlines (1-5 REQUIRED):**\n- **STRICT LIMIT: 90 characters maximum per long headline**\n- At least 1 is REQUIRED for Performance Max\n- Count characters BEFORE calling this tool!\n- Example: \"Premium Swiss Watches - Certified Authentic, Free Worldwide Shipping\" (70 chars) \u2705\n\n**\u26a0\ufe0f WILL BE REJECTED IF YOU EXCEED LIMITS - NO RETRIES!**\n**\u26a0\ufe0f COUNT CHARACTERS CAREFULLY - VALIDATION IS STRICT!**\n\n**How to Count Characters:**\n- Use extended thinking to count each text element\n- Double-check your character counts before calling\n- If close to limit, shorten it to be safe\n- Do NOT assume backend will fix it - it won't!\n- Use clear, concise language\n- Focus on user benefits\n\n**OPTIONAL Fields (use defaults if not provided):**\n- target_locations: Defaults to [\"United States\"]\n- target_languages: Defaults to [\"English\"]\n\n**\ud83c\udfa5 STEP 3.5: YouTube Videos (OPTIONAL but Highly Recommended)**\n\n**\u26a0\ufe0f IMPORTANT: Always ask the user about videos!**\n\nVideos significantly improve Performance Max campaign performance:\n- **Higher CTR**: Video ads stand out more than static images\n- **Better engagement**: Users watch videos longer\n- **Wider reach**: Serves on YouTube, Display, Discover, Gmail, Search\n- **Lower CPA**: Video ads often convert better\n\n**When to Ask:**\n- AFTER discovering existing assets\n- AFTER user uploads images\n- BEFORE calling create_pmax_campaign\n\n**What to Say:**\n\"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.\n\nIf yes:\n- You'll need videos uploaded to YouTube (public or unlisted)\n- Minimum 10 seconds duration\n- I can validate them for you\n\nIf no:\n- We can proceed with images only\n- You can add videos later if needed\"\n\n**How to Include Videos:**\n\n1. **User provides YouTube video URL or ID**\n   - Example: \"https://youtu.be/eIZtladpm6c\"\n   - Example: \"eIZtladpm6c\"\n\n2. **You validate it**\n   ```\n   Call: validate_video\n   Arguments: {\n     \"video_url_or_id\": \"eIZtladpm6c\",\n     \"platform\": \"pmax\"\n   }\n   ```\n\n3. **Include in campaign**\n   - Add `youtube_video_ids` parameter: `[\"eIZtladpm6c\"]`\n   - Can include up to 5 videos\n\n**Video Specifications:**\n- **Aspect Ratios**: Landscape (16:9), Square (1:1), Vertical (9:16)\n- **Duration**: Minimum 10 seconds (no maximum)\n- **Maximum**: 5 videos per campaign\n- **Source**: Must be on YouTube (public or unlisted, NOT private)\n- **Validation**: Happens via Google Ads API during campaign creation\n\n**If user doesn't have videos:**\n- Proceed without them (campaigns work fine without videos)\n- Mention they can add videos later\n- Note: Google may auto-generate videos from images if none provided\n\n**STEP 4: Call create_pmax_campaign**\n\nAfter you have ALL details:\n- Validate character counts yourself BEFORE calling\n- Call create_pmax_campaign with complete payload\n- Wait for response (may take 15-30 seconds)\n\n**STEP 5: Handle Response**\n\n**If SUCCESS:**\n- Celebrate with user! \ud83c\udf89\n- Show campaign ID, name, budget, status\n- Explain campaign starts PAUSED for safety\n- \ud83d\udd34 **IMMEDIATELY proceed to STEP 6 (Extensions)** \u2014 do NOT stop here!\n\n**If FAILURE:**\n\n1. **Asset Bundle Expired:**\n   - Error: \"Asset bundle not found or expired\"\n   - Action: Guide user to re-upload images\n   - Call validate_and_prepare_assets again\n\n2. **Character Limit Errors:**\n   - Error: \"Headline exceeds 30 characters\"\n   - Action: Shorten the text and try again\n   - Show user which field caused the error\n\n3. **Google Ads API Errors:**\n   - Domain not verified\n   - Budget too low\n   - Account suspended\n   - Action: Explain error clearly, provide troubleshooting steps\n\n4. **Authentication Errors:**\n   - Error: \"No Google Ads account connected\"\n   - Action: Guide user to connect their Google Ads account\n\n\ud83d\udd34 **STEP 6: MANDATORY \u2014 Add Extensions After Creation**\nAfter the campaign is successfully created, you MUST:\n1. Crawl the user's website to gather relevant page links, features, and service categories\n2. Add sitelinks: at least 4 links to key pages (add_sitelinks tool)\n3. Add callout extensions: 4-6 business highlights (add_callout_extensions tool)\n4. Add structured snippets: categorized features (add_structured_snippets tool)\n5. Verify with list_campaign_extensions to confirm all extensions are attached\nExtensions are FREE and increase ad visibility by 15-25%. NEVER skip this step.\n\n**DO NOT:**\n- Retry automatically on failure\n- Call multiple times for same campaign\n- Skip collecting user's campaign details\n- Proceed without valid asset_bundle_id\n- Skip adding extensions after campaign creation\n\n**Campaign Creation Best Practices:**\n\n\ud83d\udd34 **PRE-STEP: ALWAYS Discover Assets First**\n   - Before ANY PMAX campaign, call discover_existing_assets\n   - Show user what assets were found (images, logos with dimensions)\n   - Ask: \"Would you like to reuse these existing assets, upload new ones, or mix both?\"\n   - Based on answer:\n     - Reuse \u2192 Use existing_image_ids in create_pmax_campaign\n     - Upload new \u2192 Use validate_and_prepare_assets \u2192 asset_bundle_id\n     - Mix both \u2192 Use both parameters (hybrid mode)\n   - \ud83d\udeab NEVER skip this step\n\n1. **Research First:**\n   - Understand the business, products, audience\n   - Review website messaging\n   - Identify key selling points\n\n2. **Strategic Ad Copy:**\n   - Use action verbs\n   - Include numbers/stats if available\n   - Highlight benefits over features\n   - Create urgency when appropriate\n   - Match brand voice\n\n3. **Quality Over Quantity:**\n   - Better to have 5 great headlines than 15 mediocre ones\n   - Each headline should be unique and valuable\n   - Avoid repetition\n\n4. **User Safety:**\n   - Confirm budget with user before creating\n   - Explain campaigns start PAUSED\n   - Remind them this costs real money\n\n**Example Complete Flow:**\n\nUser: \"Create a PMax campaign for my luxury watch store\"\n\nYou: \"I'll help you create a PMax campaign! First, let me check if you have any existing assets we can reuse.\"\n\n[Call discover_existing_assets(target_domain=\"luxurywatches.com\")]\n\n[Returns: 8 images found]\n\nYou: \"Great news! I found 8 validated images from your previous campaigns:\n- 4 landscape images (1200x628)\n- 2 square images (1200x1200)\n- 2 logo images (1200x1200)\n\nWould you like to:\nA) Reuse these existing images (saves time, consistent branding)\nB) Upload new images\nC) Mix both (reuse some + add new ones)\"\n\n[User chooses A - Reuse]\n\nYou: \"Perfect! Now I need a few details:\n1. What daily budget would you like? (minimum $10, recommended $50+)\n2. What's your landing page URL?\n3. What locations should we target? (e.g., 'Bay Area', 'San Francisco', 'United States')\n\nI'll create compelling headlines and descriptions based on your luxury watch business.\"\n\n[User provides budget: $75, URL: https://luxurywatches.com, Location: Bay Area]\n\nYou: \"Great! Here's what I'll create:\n\n**Campaign Name:** Premium Watches Campaign 2025\n**Budget:** $75/day\n**Landing Page:** https://luxurywatches.com\n**Locations:** Bay Area (targeted precisely)\n\n**Headlines I've generated:**\n- Premium Luxury Watches\n- Certified Authentic\n- Free Worldwide Shipping\n[...12 more]\n\n**Descriptions:**\n- Shop authentic Swiss timepieces with lifetime warranty and expert curation.\n- Discover premium watches from top brands. Free shipping. 30-day returns.\n[...2 more]\n\n**Using existing images:** 8 validated images (reuse mode)\n\nReady to create this campaign? (It will start PAUSED so you can review first)\"\n\n[User confirms]\n\n[Call create_pmax_campaign with existing_image_ids from discovery]\n\n**Returns:**\n- Campaign ID, name, status\n- Asset group summary\n- Next steps for activation\n\n**Execution Time:** 15-30 seconds (direct backend API call)\n\n**Authentication:** Required (MCP OAuth 2.1)\n\n**CRITICAL REMINDERS:**\n- Asset bundle expires after 1 hour\n- Campaign starts PAUSED for user safety\n- This costs real money - be transparent\n- Never retry on failure - report error to user\n- Provide excellent user experience throughout",
        "operationId": "execute_create_pmax_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "asset_bundle_id": "string",
                  "budget_daily": 1.0,
                  "business_name": "string",
                  "campaign_name": "string",
                  "descriptions": [
                    "string"
                  ],
                  "existing_image_ids": [
                    "customers/1234567890/assets/12345678901",
                    "customers/1234567890/assets/12345678902"
                  ],
                  "final_url": "string",
                  "headlines": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for create_pmax_campaign tool.\n\nSupports two modes:\n1. Single asset group (flat fields \u2014 backward compatible)\n2. Multiple asset groups (asset_groups array \u2014 #183)\n\nIf asset_groups is provided, it takes priority over flat fields.",
                    "properties": {
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID from validate_and_prepare_assets tool (UUID format). Optional if existing_image_ids provided.",
                        "title": "Asset Bundle Id"
                      },
                      "asset_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": true,
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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), descriptions (2-4), long_headlines (1-5), business_name, final_url, existing_image_ids (optional), search_themes (optional), audience_signals (optional). Max 100 groups per campaign. All groups share the same campaign budget and bidding strategy. Example: [{'group_name': 'SaaS Founders', 'headlines': ['AI Ad Manager', 'Skip Dashboards', 'Automate Ads'], 'descriptions': ['Manage ads via AI chat.', '100+ tools.'], 'long_headlines': ['AI Ad Management'], 'business_name': 'Adspirer', 'final_url': 'https://adspirer.com', 'search_themes': ['AI ads'], 'audience_signals': {'in_market_audience_ids': [80432]}}]",
                        "title": "Asset Groups"
                      },
                      "audience_signals": {
                        "anyOf": [
                          {
                            "additionalProperties": true,
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 audiences (e.g., 'Vehicles' instead of 'Jewelry') and waste the customer's ad budget. If search_audiences returns no results, skip audience signals entirely rather than guessing IDs. Keys: 'in_market_audience_ids' (List[int]), 'affinity_audience_ids' (List[int]), 'custom_audience_ids' (List[str] resource names), 'user_list_ids' (List[str] userList resource names for remarketing/customer match -- e.g. 'customers/123/userLists/456' from search_audiences), 'audience_name' (str). Example: {'in_market_audience_ids': [80432, 80210], 'user_list_ids': ['customers/123/userLists/456'], 'audience_name': 'SaaS Buyers'}",
                        "title": "Audience Signals"
                      },
                      "bidding_strategy": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 data. Recommended: MAXIMIZE_CONVERSIONS for most new campaigns.",
                        "title": "Bidding Strategy"
                      },
                      "budget_daily": {
                        "description": "Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies \u2014 pass the user's amount as-is. Example: if user says '\u20b95000/day', pass 5000 (not a USD conversion). The Google Ads API interprets this in the account's currency automatically. Google recommends minimum ~$50/day USD equivalent for Performance Max campaigns.",
                        "minimum": 1.0,
                        "title": "Budget Daily",
                        "type": "number"
                      },
                      "business_name": {
                        "anyOf": [
                          {
                            "maxLength": 25,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Business name, max 25 characters. REQUIRED for single-group mode. Omit if using asset_groups array. Commas ARE allowed.",
                        "title": "Business Name"
                      },
                      "campaign_name": {
                        "description": "Campaign name (e.g., 'Premium Watches Summer 2025')",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 expert curation.\"] (63 chars). COUNT CHARACTERS BEFORE CALLING - ANY over 80 will be REJECTED!",
                        "title": "Descriptions"
                      },
                      "existing_image_ids": {
                        "anyOf": [
                          {
                            "additionalProperties": {
                              "items": {
                                "type": "string"
                              },
                              "type": "array"
                            },
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Resource names of existing images to reuse (from discover_existing_assets). Optional if asset_bundle_id provided. \u26a0\ufe0f MUST include 'logos_square' \u2014 Google Ads REQUIRES a logo for every PMax asset group. If discover_existing_assets found no logos, use hybrid mode: upload a logo via validate_and_prepare_assets (asset_bundle_id) + pass existing_image_ids for other images. Example: {'marketing_images_landscape': ['customers/123/assets/456'], 'marketing_images_square': ['customers/123/assets/789'], 'logos_square': ['customers/123/assets/101']}",
                        "title": "Existing Image Ids"
                      },
                      "final_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Landing page URL. REQUIRED for single-group mode. Omit if using asset_groups array.",
                        "title": "Final Url"
                      },
                      "headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 over 30 will be REJECTED!",
                        "title": "Headlines"
                      },
                      "long_headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 allowed. Example: [\"Premium Swiss Watches - Certified Authentic, Free Shipping\"]. COUNT CHARACTERS! ANY over 90 will be REJECTED!",
                        "title": "Long Headlines"
                      },
                      "search_themes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Search themes hint Google AI about what your customers search for. Max 50 per asset group. NOT keywords \u2014 no match types, no bids. Derive from keyword research, business profile, or user input. Example: ['AI ad management', 'automate google ads', 'MCP advertising tool']. Duplicates and blanks are filtered automatically. Pass [] (empty list) to explicitly skip search themes (e.g., remarketing-only campaigns). If omitted (null), themes are auto-derived from headlines.",
                        "title": "Search Themes"
                      },
                      "target_languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Language targets (e.g., ['English', 'Spanish']). Defaults to English.",
                        "title": "Target Languages"
                      },
                      "target_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Target Locations"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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'.",
                        "title": "Target Roas"
                      },
                      "youtube_video_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 created without them.",
                        "title": "Youtube Video Ids"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "budget_daily"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_pmax_campaign)"
                  },
                  "success": true,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_pmax_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_pmax_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_pmax_campaign"
      }
    },
    "/api/v1/tools/create_search_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\n\ud83d\udd04 LONG-RUNNING TOOL: Creates a Google Ads Search campaign with full structure.\n\nEmits MCP progress updates during authentication and campaign creation (typically 5-10 seconds).\nProgress stages: validate \u2192 commit.\n\n\u26a0\ufe0f CRITICAL WARNING \u26a0\ufe0f\n- Call this tool ONLY ONCE per campaign\n- Creates REAL campaigns that cost REAL money\n- Do NOT retry automatically if errors occur\n- Report errors to user instead of retrying\n\n\u26a0\ufe0f GOOGLE ADS POLICY NOTE:\nAvoid keywords related to health conditions, medical treatments, financial hardship, or political topics. These may trigger policy violations. Use general service terms instead.\nExample: Use \"senior care services\" not \"nursing care\", \"home services\" not \"medical services\"\n\nYOUR ROLE: Expert Google Ads Campaign Strategist\n\nBEFORE calling this tool, YOU MUST:\n1. **Research the business thoroughly:**\n   - Understand products/services, target audience, value propositions\n   - Analyze competitive landscape and market positioning\n   - Review website messaging and offers\n   - Consider seasonal factors and current trends\n\n2. **\ud83d\udd0d KEYWORD RESEARCH (CRITICAL - Call research_keywords tool FIRST):**\n   Do NOT use generic SEO keywords. Get HIGH-INTENT keywords with real CPC data!\n\n   **YOU MUST call the research_keywords tool BEFORE this tool:**\n\n   Steps:\n   a) Call the research_keywords tool:\n      ```\n      Tool: research_keywords\n      Arguments: {\n        \"business_description\": \"Emergency plumbing services for homeowners\",\n        \"website_url\": \"https://example.com\",\n        \"target_location\": \"New York, NY\"\n      }\n      ```\n\n   b) The tool will return:\n      - Keyword table with dynamic CPC thresholds (adapts to industry)\n      - HIGH/MEDIUM/LOW intent keywords\n      - Budget recommendations (Conservative/Moderate/Aggressive)\n      - Top 15-20 recommended keywords\n\n   c) Show the keyword table to the user\n\n   d) Ask user if they want to modify keyword selection:\n      - Can add/remove specific keywords\n      - Can use only HIGH intent keywords\n      - Default: use recommended keywords\n\n   e) Extract keyword texts from the research_keywords result\n      - Use the recommended keywords for this tool's 'keywords' parameter\n      - Max 15-20 keywords per ad group\n      - Will use BROAD match automatically (Google's 2025 recommendation)\n\n3. **Analyze Target Demographics:**\n   For each ad group, consider who the ideal customer is:\n   - **Age**: What age groups are most likely to buy? (e.g., luxury products \u2192 35-54, gaming \u2192 18-34)\n   - **Gender**: Is the product/service gender-specific? (e.g., women's fashion \u2192 FEMALE)\n   - **Income**: What income level can afford this? (e.g., luxury \u2192 TOP_10/80_90, budget \u2192 0_50/50_60)\n\n   **Examples:**\n   - Luxury watches: Males 35-54, income top 20%\n   - Budget fashion: Females 18-34, all income levels\n   - Senior services: Age 65+, income 50%+\n   - Gaming products: 18-34, all genders, middle income\n\n   If unclear, leave demographics empty for broad reach.\n\n3. **Generate complete campaign structure with ALL details:**\n\n**Required Fields (all mandatory):**\n1. **campaign_name** - Unique descriptive name\n2. **business_description** - What the business sells\n3. **website_url** - Landing page URL\n4. **budget_daily** - Daily budget in the account's native currency (do NOT convert \u2014 pass user's amount as-is)\n5. **target_locations** - Geographic targets (cities/states/countries)\n6. **ad_groups** - Array of 2-4 ad groups, each containing:\n   - name: Ad group theme (e.g., \"Premium Care Services\")\n   - keywords: 15-20 keywords from Keyword Planner research (will use BROAD match - Google's 2025 recommendation)\n   - headlines: Exactly 15 headlines (MAXIMUM 30 chars each - NO EXCEPTIONS)\n   - descriptions: Exactly 4 descriptions (MAXIMUM 80 chars each - NO EXCEPTIONS)\n   - final_url: Landing page for this ad group\n   - sitelinks (optional): 2-4 additional links below ad\n   - audiences (optional): Demographics and audience targeting\n     - demographics: {age_ranges: [...], genders: [...], income_ranges: [...]}\n     - custom_audiences: [...], in_market_audiences: [...], affinity_audiences: [...]\n7. **negative_keywords** (optional) - Terms to exclude\n\n**\ud83d\udea8 CRITICAL CHARACTER LIMITS - COUNT BEFORE CALLING \ud83d\udea8**\n- **Headlines:** MAXIMUM 30 characters (NO EXCEPTIONS - will be rejected!)\n- **Descriptions:** MAXIMUM 80 characters (NO EXCEPTIONS - will be rejected!)\n- **Long Headlines (PMAX):** MAXIMUM 90 characters (NO EXCEPTIONS - will be rejected!)\n- Generate ALL content BEFORE calling this tool\n- COUNT the characters in each headline and description\n- If ANY exceed limits, you MUST shorten them BEFORE calling\n- Do NOT rely on retry - fix it the first time!\n- \u2705 Commas ARE ALLOWED and encouraged for natural language\n- \u274c Do NOT use pipes (|) as separators - they will appear literally in ads\n- \u274c Do NOT send comma-delimited strings - use proper JSON arrays\n- Example: [\"Expert Care, 24/7 Support\", \"Premium Service Available\"]\n- Do NOT call this tool multiple times - it creates REAL campaigns\n- If errors occur, show them to the user - do NOT retry automatically\n- Each call costs money and creates actual Google Ads campaigns\n\n**SITELINK RULES (if using):**\n\u26a0\ufe0f Google will REJECT sitelinks that violate these rules:\n- ALL sitelink URLs MUST be same domain as final_url\n  \u2705 Good: final_url='https://example.com' \u2192 sitelink='https://example.com/shipping'\n  \u274c Bad: final_url='https://example.com' \u2192 sitelink='https://different-site.com'\n- Each sitelink must link to a UNIQUE, WORKING page (no duplicates, no 404s)\n- Link text must be DESCRIPTIVE (not \"Click Here\", \"Learn More\")\n- If you add description1, you MUST also add description2\n- Only add sitelinks if you've verified the URLs exist and work\n\n**Execution Time:** 15-30 seconds\n\n**What happens:**\n- Campaign created immediately in Google Ads\n- Real money will be spent on the campaign\n- Returns campaign_id for tracking\n\n**Use for:**\n- Launching new advertising campaigns\n- Testing new market segments\n- Scaling successful products",
        "operationId": "execute_create_search_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_groups": [
                    {
                      "audiences": {
                        "affinity_audiences": null,
                        "custom_audiences": null,
                        "demographics": null,
                        "in_market_audiences": null
                      },
                      "descriptions": [
                        "string"
                      ],
                      "final_url": "https://example.com",
                      "headlines": [
                        "string"
                      ],
                      "keywords": [
                        "string"
                      ],
                      "name": "string",
                      "sitelinks": [
                        {
                          "description1": null,
                          "description2": null,
                          "final_url": null,
                          "link_text": null
                        }
                      ]
                    }
                  ],
                  "bidding_strategy": "MAXIMIZE_CONVERSIONS",
                  "budget_daily": 1.0,
                  "business_description": "string",
                  "campaign_name": "string",
                  "customer_id": "string",
                  "negative_keywords": [
                    "string"
                  ],
                  "objective": "CONVERSIONS",
                  "target_locations": [
                    "string"
                  ],
                  "target_roas": 1.0,
                  "website_url": "https://example.com"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "$defs": {
                      "AdGroupInput": {
                        "properties": {
                          "audiences": {
                            "anyOf": [
                              {
                                "$ref": "#/$defs/AudienceTargeting"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Optional: Audience targeting to reach specific user segments. Leave empty for broad reach. Use if you have specific audience IDs from Google Ads."
                          },
                          "descriptions": {
                            "description": "EXACTLY 4 descriptions required (max 80 chars each - STRICT LIMIT). Google Ads requires 4 descriptions for Responsive Search Ads. ANY description over 80 chars will be REJECTED. Commas ARE allowed within descriptions. Example: [\"Discover handcrafted timepieces for collectors.\"] (50 chars). MUST BE JSON ARRAY. COUNT CHARACTERS BEFORE CALLING!",
                            "items": {
                              "type": "string"
                            },
                            "maxItems": 4,
                            "minItems": 4,
                            "title": "Descriptions",
                            "type": "array"
                          },
                          "final_url": {
                            "description": "Landing page URL for this ad group (usually the website URL or a specific page)",
                            "title": "Final Url",
                            "type": "string"
                          },
                          "headlines": {
                            "description": "EXACTLY 15 headlines required (max 30 chars each). Google Ads requires 15 headlines for Responsive Search Ads. Commas ARE allowed within headlines. Example: [\"Premium Watches, Since 1950\", \"Luxury Timepieces\", ...]. MUST BE JSON ARRAY - do NOT send comma-delimited strings.",
                            "items": {
                              "type": "string"
                            },
                            "maxItems": 15,
                            "minItems": 15,
                            "title": "Headlines",
                            "type": "array"
                          },
                          "keywords": {
                            "description": "Keywords for this ad group (10-20 keywords recommended, minimum 5). Should come from Google Keyword Planner research. Will use BROAD match.",
                            "items": {
                              "type": "string"
                            },
                            "minItems": 5,
                            "title": "Keywords",
                            "type": "array"
                          },
                          "name": {
                            "description": "Ad group name (e.g., 'Premium Watches', 'Luxury Timepieces')",
                            "title": "Name",
                            "type": "string"
                          },
                          "sitelinks": {
                            "anyOf": [
                              {
                                "items": {
                                  "$ref": "#/$defs/SitelinkExtension"
                                },
                                "maxItems": 4,
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Optional: 2-4 sitelink extensions (additional links below ad). IMPORTANT: All sitelink URLs MUST be same domain as final_url. Example: [{'link_text': 'Free Shipping', 'final_url': 'https://example.com/shipping', 'description1': 'Fast delivery', 'description2': 'Order by 5pm'}]",
                            "title": "Sitelinks"
                          }
                        },
                        "required": [
                          "name",
                          "keywords",
                          "headlines",
                          "descriptions",
                          "final_url"
                        ],
                        "title": "AdGroupInput",
                        "type": "object"
                      },
                      "AudienceTargeting": {
                        "description": "Audience targeting for ad groups - reach specific user segments",
                        "properties": {
                          "affinity_audiences": {
                            "anyOf": [
                              {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Affinity audience IDs. Users with demonstrated interest in related topics. Leave empty to skip audience targeting.",
                            "title": "Affinity Audiences"
                          },
                          "custom_audiences": {
                            "anyOf": [
                              {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Custom audience IDs from your Google Ads account. Leave empty if you don't have pre-created custom audiences.",
                            "title": "Custom Audiences"
                          },
                          "demographics": {
                            "anyOf": [
                              {
                                "$ref": "#/$defs/DemographicTargeting"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Optional: Target specific demographics (age, gender, income). Analyze the business to determine ideal customer profile. Leave empty for broad demographic reach."
                          },
                          "in_market_audiences": {
                            "anyOf": [
                              {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "In-market audience IDs. Users actively researching/comparing products. Leave empty to skip audience targeting.",
                            "title": "In Market Audiences"
                          }
                        },
                        "title": "AudienceTargeting",
                        "type": "object"
                      },
                      "DemographicTargeting": {
                        "description": "Demographic targeting - target specific age groups, genders, and income levels",
                        "properties": {
                          "age_ranges": {
                            "anyOf": [
                              {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Target specific age groups. Options: 'AGE_RANGE_18_24', 'AGE_RANGE_25_34', 'AGE_RANGE_35_44', 'AGE_RANGE_45_54', 'AGE_RANGE_55_64', 'AGE_RANGE_65_UP'. Leave empty to target all ages. Example: ['AGE_RANGE_25_34', 'AGE_RANGE_35_44'] for 25-44 year olds.",
                            "title": "Age Ranges"
                          },
                          "genders": {
                            "anyOf": [
                              {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Target specific genders. Options: 'MALE', 'FEMALE'. Leave empty to target all genders. Example: ['MALE'] for male-only targeting.",
                            "title": "Genders"
                          },
                          "income_ranges": {
                            "anyOf": [
                              {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Target household income levels. Options: 'INCOME_RANGE_0_50' (Lower 50%), 'INCOME_RANGE_50_60' (50-60%), 'INCOME_RANGE_60_70' (60-70%), 'INCOME_RANGE_70_80' (70-80%), 'INCOME_RANGE_80_90' (80-90%), 'INCOME_RANGE_TOP_10' (Top 10%). Leave empty to target all income levels. Example: ['INCOME_RANGE_TOP_10', 'INCOME_RANGE_80_90'] for top 20% earners.",
                            "title": "Income Ranges"
                          }
                        },
                        "title": "DemographicTargeting",
                        "type": "object"
                      },
                      "SitelinkExtension": {
                        "description": "Sitelink extension - additional links that appear below your ad\n\nCRITICAL SITELINK RULES:\n- URL MUST be same domain as main ad (e.g., if ad goes to example.com, sitelinks must go to example.com/*)\n- Link text must be DESCRIPTIVE and RELEVANT to the page it links to\n- Each sitelink must go to a UNIQUE URL (no duplicates)\n- URLs must be HTTPS and WORKING pages (Google will reject broken links)\n- Don't use generic terms like \"Click Here\" - be specific\n- Common violations: wrong domain, broken links, duplicate URLs, misleading text",
                        "properties": {
                          "description1": {
                            "anyOf": [
                              {
                                "maxLength": 35,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "First description line (max 35 chars, optional). Adds context to the link. Example: 'Fast nationwide delivery'",
                            "title": "Description1"
                          },
                          "description2": {
                            "anyOf": [
                              {
                                "maxLength": 35,
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ],
                            "default": null,
                            "description": "Second description line (max 35 chars, required if description1 provided). Example: 'Orders ship within 24 hours'",
                            "title": "Description2"
                          },
                          "final_url": {
                            "description": "HTTPS URL for this sitelink. MUST be same domain as main ad URL. Must be a working page (Google validates). Each sitelink needs UNIQUE URL. Example: If ad goes to 'https://example.com', sitelink could be 'https://example.com/shipping'",
                            "title": "Final Url",
                            "type": "string"
                          },
                          "link_text": {
                            "description": "Link text displayed (max 25 chars). MUST be descriptive and match the page content. Good: 'Free Shipping Info', 'Pricing Plans', 'Contact Support'. Bad: 'Click Here', 'Learn More', 'Website'",
                            "maxLength": 25,
                            "title": "Link Text",
                            "type": "string"
                          }
                        },
                        "required": [
                          "link_text",
                          "final_url"
                        ],
                        "title": "SitelinkExtension",
                        "type": "object"
                      }
                    },
                    "properties": {
                      "ad_groups": {
                        "description": "1-4 themed ad groups, each with keywords, headlines, and descriptions. Each ad group represents a theme/audience segment.",
                        "items": {
                          "$ref": "#/$defs/AdGroupInput"
                        },
                        "title": "Ad Groups",
                        "type": "array"
                      },
                      "bidding_strategy": {
                        "default": "MAXIMIZE_CONVERSIONS",
                        "description": "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_CONVERSIONS for new campaigns.",
                        "title": "Bidding Strategy",
                        "type": "string"
                      },
                      "budget_daily": {
                        "description": "Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies \u2014 pass the user's amount as-is. Example: if user says '\u20b91000/day', pass 1000 (not a USD conversion). The Google Ads API interprets this in the account's currency automatically. Google recommends minimum ~$10/day USD equivalent for Search campaigns.",
                        "minimum": 1.0,
                        "title": "Budget Daily",
                        "type": "number"
                      },
                      "business_description": {
                        "description": "What does your business sell? (e.g., 'Luxury watch retailer specializing in Rolex')",
                        "title": "Business Description",
                        "type": "string"
                      },
                      "campaign_name": {
                        "description": "Campaign name (e.g., 'Luxury Watches Q4 2025')",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "negative_keywords": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign-level negative keywords (optional, e.g., ['free', 'cheap', 'used'])",
                        "title": "Negative Keywords"
                      },
                      "objective": {
                        "default": "CONVERSIONS",
                        "description": "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.",
                        "title": "Objective",
                        "type": "string"
                      },
                      "target_locations": {
                        "description": "Geographic targets \u2014 supports countries, states, cities, and regions globally. Examples: ['India'], ['Mumbai, India'], ['New York, NY'], ['United States', 'Canada']",
                        "items": {
                          "type": "string"
                        },
                        "title": "Target Locations",
                        "type": "array"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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'.",
                        "title": "Target Roas"
                      },
                      "website_url": {
                        "description": "Your business website URL (landing page for ads)",
                        "title": "Website Url",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "business_description",
                      "website_url",
                      "budget_daily",
                      "target_locations",
                      "ad_groups"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_search_campaign)"
                  },
                  "success": true,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_search_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_search_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_search_campaign"
      }
    },
    "/api/v1/tools/create_tiktok_campaign/execute": {
      "post": {
        "description": "User wants to create a TikTok ad campaign with IMAGES, Spark Ads, or Carousel ads (not video).\n\nLONG-RUNNING TOOL: Creates a TikTok In-Feed ad campaign with image creatives, Spark Ads (boost organic posts), or Carousel ads.\n\nSupported creative types:\n- Standard Image Ads: Provide asset_bundle_id or existing_image_ids\n- Spark Ads: Provide tiktok_item_id to boost an existing organic TikTok post as a paid ad\n- Carousel Ads: First call create_tiktok_carousel_card to get a card_id, then provide card_id + card_type here\n- App Promotion: Set objective=APP_PROMOTION + app_id for app install campaigns\n\nEmits MCP progress updates during campaign creation (typically 15-30 seconds).\nProgress stages: validate - commit.\n\nCRITICAL WARNING:\n- Call this tool ONLY ONCE per campaign\n- Creates REAL campaigns that cost REAL money\n- Do NOT retry automatically if errors occur\n- Report errors to user instead of retrying\n\nYOUR ROLE: TikTok Ads Campaign Strategist\n\nBEFORE calling this tool, YOU MUST:\n\n1. Prepare Assets (choose ONE method):\n   - METHOD A: Upload new images\n     - Call `validate_and_prepare_tiktok_assets` with image URLs\n     - Get `asset_bundle_id`\n     - Use in create_tiktok_campaign\n   - METHOD B: Reuse existing images\n     - Call `discover_tiktok_assets` to browse library\n     - Get `existing_image_ids`\n     - Use in create_tiktok_campaign\n\n2. Research the business:\n   - Understand products/services, target audience\n   - Review brand messaging and offers\n   - Consider TikTok-specific creative best practices (authentic, engaging, mobile-first)\n\n3. Craft compelling ad creative:\n   - Ad text: 12-100 chars (50 or less recommended for no truncation)\n   - Write for TikTok's young, mobile audience\n   - Use casual, authentic language\n   - Include clear call-to-action\n\n4. Define targeting:\n   - Locations: Default is USA, can specify others\n   - Age groups: TikTok audience skews younger (18-34)\n   - Gender: Unless product is gender-specific, use GENDER_UNLIMITED\n\nRequired Fields:\n1. campaign_name - Descriptive name (auto-suffixed with timestamp)\n2. budget_daily - Minimum $20/day for image ads\n3. ad_text - Main message (12-100 chars, 50 recommended)\n4. display_name - Brand name (max 40 chars)\n5. landing_page_url - HTTPS URL where users land\n6. asset_bundle_id OR existing_image_ids - Choose one\n\nOptional Fields:\n- target_locations - List of TikTok location IDs (default: USA)\n- target_age_groups - Age ranges to target\n- target_gender - Gender targeting (default: all)\n- advertiser_id - TikTok advertiser account\n\nWhat happens when you call this tool:\n1. Creates Campaign with daily budget\n2. Creates Ad Group with targeting and auto-bidding\n3. Uploads images to TikTok (if using asset_bundle_id) OR links existing images\n4. Creates Ad with your creative\n5. Campaign goes ACTIVE immediately\n\nIdempotency Support:\n- If creation fails partway, partial state is logged (campaign_id, ad_group_id)\n- You can retry with same parameters - system will resume from checkpoint\n- Prevents duplicate campaigns\n\nExecution time: 15-30 seconds (TikTok API has 10-second image processing wait)\n\nError handling:\n- If validation fails: Error message with specific issue\n- If TikTok API fails: Error with partial state IDs for manual cleanup\n- Check TikTok Ads Manager for partial campaigns\n\nBest Practices:\n- Start with smaller budgets ($20-50/day) and scale what works\n- Test multiple ad variations (different images, text)\n- Monitor performance in TikTok Ads Manager after 2-3 days\n- TikTok learning phase is ~7 days - don't make changes too quickly\n- Use vertical 9:16 images - horizontal images won't work well",
        "operationId": "execute_create_tiktok_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_text": "string",
                  "app_id": "string",
                  "app_promotion_type": "string",
                  "budget_daily": 1.0,
                  "budget_lifetime": 1.0,
                  "campaign_name": "string",
                  "display_name": "string",
                  "landing_page_url": "https://example.com",
                  "objective": "TRAFFIC",
                  "schedule_end_time": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating TikTok In-Feed image ad campaigns",
                    "properties": {
                      "ad_text": {
                        "description": "Ad text (1-100 characters). Recommended: 50 characters or less for no truncation.",
                        "maxLength": 100,
                        "minLength": 1,
                        "title": "Ad Text",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "app_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok app ID for APP_PROMOTION campaigns. Required when objective is APP_PROMOTION.",
                        "title": "App Id"
                      },
                      "app_promotion_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "App promotion type for APP_PROMOTION campaigns. Options: 'APP_INSTALL' (new installs), 'APP_RETARGETING' (re-engage existing users). Default: APP_INSTALL",
                        "title": "App Promotion Type"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Asset bundle ID from validate_and_prepare_tiktok_assets tool. Use this for NEW image uploads. Mutually exclusive with existing_image_ids.",
                        "title": "Asset Bundle Id"
                      },
                      "audience_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom audience IDs to include (DMP audiences, lookalikes).",
                        "title": "Audience Ids"
                      },
                      "budget_daily": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Daily budget in account currency (minimum varies by region, typically $20-50/day). Mutually exclusive with budget_lifetime.",
                        "title": "Budget Daily"
                      },
                      "budget_lifetime": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lifetime budget in account currency (total spend over campaign duration). Requires schedule_end_time. Mutually exclusive with budget_daily.",
                        "title": "Budget Lifetime"
                      },
                      "budget_optimize_on": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Budget Optimize On"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Call To Action"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "card_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Card Id"
                      },
                      "card_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel card type: 'IMAGE' (image carousel) or 'PRODUCT' (product catalog carousel). Required when card_id is provided.",
                        "title": "Card Type"
                      },
                      "comment_disabled": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Disable comments on ads. Default: false (comments allowed).",
                        "title": "Comment Disabled"
                      },
                      "display_name": {
                        "description": "Brand/business name displayed on the ad (max 40 characters)",
                        "maxLength": 40,
                        "title": "Display Name",
                        "type": "string"
                      },
                      "excluded_audience_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom audience IDs to exclude.",
                        "title": "Excluded Audience Ids"
                      },
                      "existing_image_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Existing Image Ids"
                      },
                      "interest_category_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Interest category IDs for interest-based targeting. Get IDs from TikTok Ads Manager or search_tiktok_interests tool.",
                        "title": "Interest Category Ids"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL (must be HTTPS).",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Language targeting codes (e.g., ['en', 'es']). Default: all languages.",
                        "title": "Languages"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "TRAFFIC",
                        "description": "Campaign objective. Options: 'TRAFFIC' (website visits), 'CONVERSIONS' (website conversions), 'LEAD_GENERATION' (leads), 'REACH' (maximum impressions), 'VIDEO_VIEWS' (video views), 'APP_PROMOTION' (app installs \u2014 requires app_id). Default: TRAFFIC",
                        "title": "Objective"
                      },
                      "operating_systems": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target device OS. Options: 'ANDROID', 'IOS'. Default: all.",
                        "title": "Operating Systems"
                      },
                      "optimization_event": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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, PAGE_VISIT, CLICK_LANDING_PAGE, ON_WEB_SUBSCRIBE, PHONE_CONNECT, CONSULT, SEARCH, SUBSCRIBE, DOWNLOAD_FINISH. Default: COMPLETE_PAYMENT",
                        "title": "Optimization Event"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok Pixel ID for conversion tracking. Required for CONVERSIONS objective.",
                        "title": "Pixel Id"
                      },
                      "placement_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Placement type: 'PLACEMENT_TYPE_AUTOMATIC' (recommended) or 'PLACEMENT_TYPE_NORMAL' (manual). Default: PLACEMENT_TYPE_AUTOMATIC.",
                        "title": "Placement Type"
                      },
                      "placements": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Manual placement selection (when placement_type=PLACEMENT_TYPE_NORMAL). Options: 'PLACEMENT_TIKTOK', 'PLACEMENT_PANGLE', 'PLACEMENT_GLOBAL_APP_BUNDLE'.",
                        "title": "Placements"
                      },
                      "schedule_end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign end time in format 'YYYY-MM-DD HH:MM:SS'. Required when using budget_lifetime. Optional with budget_daily.",
                        "title": "Schedule End Time"
                      },
                      "target_age_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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+.",
                        "title": "Target Age Groups"
                      },
                      "target_gender": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "GENDER_UNLIMITED",
                        "description": "Gender targeting: 'GENDER_UNLIMITED', 'GENDER_MALE', 'GENDER_FEMALE'.",
                        "title": "Target Gender"
                      },
                      "target_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of TikTok location IDs. Default: ['6252001'] (United States). Common IDs: USA=6252001, UK=2635167, Canada=6251999, Australia=2077456.",
                        "title": "Target Locations"
                      },
                      "tiktok_item_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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_image_ids are NOT required.",
                        "title": "Tiktok Item Id"
                      },
                      "video_download_disabled": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Disable video download on TikTok. Default: false (users can download).",
                        "title": "Video Download Disabled"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "ad_text",
                      "display_name",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_tiktok_campaign)"
                  },
                  "success": true,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_tiktok_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a TikTok ad campaign with IMAGES, Spark Ads, or Carousel ads (not video) [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_tiktok_campaign"
      }
    },
    "/api/v1/tools/create_tiktok_carousel_card/execute": {
      "post": {
        "description": "Create a carousel card from multiple images for TikTok carousel ads.\n\nIMPORTANT: Call this tool BEFORE creating a carousel campaign or carousel ad.\n\nCarousel ads show 2-10 swipeable image cards. Each card can have its own ad text and landing page.\n\nWorkflow for carousel campaigns:\n1. Get TikTok image IDs:\n   - Option A: upload_tiktok_images with public image URLs (uploads to TikTok, returns image_ids)\n   - Option B: discover_tiktok_assets to find existing images in your library\n2. Call this tool with those image_ids to create a carousel card (get card_id back)\n3. Use the card_id in create_tiktok_campaign or add_tiktok_ad with card_type='IMAGE'\n\nRequired: image_ids (2-10 TikTok image IDs).\nOptional: ad_texts (one per card), landing_page_urls (one per card), call_to_action.",
        "operationId": "execute_create_tiktok_carousel_card",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_texts": [
                    "string"
                  ],
                  "advertiser_id": "string",
                  "call_to_action": "string",
                  "card_type": "IMAGE",
                  "image_ids": [
                    "string"
                  ],
                  "landing_page_urls": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for creating a TikTok carousel card from multiple images",
                    "properties": {
                      "ad_texts": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Ad text for each card (one per image). If fewer texts than images, the first text is reused.",
                        "title": "Ad Texts"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID (optional).",
                        "title": "Advertiser Id"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "CTA button for all cards: LEARN_MORE, SHOP_NOW, SIGN_UP, etc.",
                        "title": "Call To Action"
                      },
                      "card_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "IMAGE",
                        "description": "Card type: 'IMAGE' (image carousel) or 'PRODUCT' (product catalog). Default: IMAGE.",
                        "title": "Card Type"
                      },
                      "image_ids": {
                        "description": "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.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Image Ids",
                        "type": "array"
                      },
                      "landing_page_urls": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Landing page URL for each card (one per image, must be HTTPS). If fewer URLs than images, the first URL is reused.",
                        "title": "Landing Page Urls"
                      }
                    },
                    "required": [
                      "image_ids"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_tiktok_carousel_card)"
                  },
                  "success": true,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_tiktok_carousel_card",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_carousel_card"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Create a carousel card from multiple images for TikTok carousel ads [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_tiktok_carousel_card"
      }
    },
    "/api/v1/tools/create_tiktok_video_campaign/execute": {
      "post": {
        "description": "User wants to create a TikTok ad campaign with a VIDEO (not images). Also supports Spark Ads and Carousel with video.\n\nLONG-RUNNING TOOL: Creates a TikTok In-Feed video ad campaign. Also supports Spark Ads (boost organic posts) and Carousel ads.\n\nAdditional creative types supported:\n- Spark Ads: Provide tiktok_item_id to boost an existing organic TikTok post as a paid ad\n- Carousel Ads: First call create_tiktok_carousel_card to get a card_id, then provide card_id + card_type here\n- App Promotion: Set objective=APP_PROMOTION + app_id for app install campaigns\n\nEmits MCP progress updates during campaign creation (typically 30-60 seconds).\nProgress stages: validate - upload - commit.\n\nCRITICAL WARNING:\n- Call this tool ONLY ONCE per campaign\n- Creates REAL campaigns that cost REAL money\n- Do NOT retry automatically if errors occur\n- Report errors to user instead of retrying\n\nYOUR ROLE: TikTok Video Ads Campaign Strategist\n\nCRITICAL PRE-FLIGHT CHECKLIST - DO NOT SKIP:\n\n1. Fix Video URL Format (CRITICAL!):\n\n   Check if user provided Google Drive link:\n   - BAD: https://drive.google.com/file/d/{FILE_ID}/view?usp=sharing\n   - GOOD: https://drive.google.com/uc?export=download&id={FILE_ID}\n\n   YOU MUST CONVERT Google Drive links! Extract FILE_ID and rebuild URL.\n   Example:\n   - Input: https://drive.google.com/file/d/1jTOcNDe1UfvlUka5VpD2RAblNpQ3FGm_/view?usp=sharing\n   - Extract ID: 1jTOcNDe1UfvlUka5VpD2RAblNpQ3FGm_\n   - Output: https://drive.google.com/uc?export=download&id=1jTOcNDe1UfvlUka5VpD2RAblNpQ3FGm_\n\n   Other common fixes:\n   - Dropbox: Add ?dl=1 to end of URL (not ?dl=0)\n   - Vimeo: Use direct MP4 link, not player embed\n   - YouTube: NOT SUPPORTED - TikTok cannot download from YouTube\n\n2. Validate Video URL:\n   - ALWAYS call `validate_video(video_url, platform='tiktok')` FIRST\n   - Check response Content-Type:\n     * GOOD: \"video/mp4\", \"video/quicktime\", \"video/x-msvideo\"\n     * BAD: \"text/html\" (means it's a web page, not direct video)\n   - If Content-Type is text/html, STOP and ask user for direct download link\n   - Video requirements:\n     * Duration: 5-60 seconds (TikTok sweet spot: 9-15 seconds)\n     * File size: Max 500MB\n     * Aspect ratio: 9:16 (vertical) STRONGLY preferred for TikTok\n     * Formats: MP4, MOV, MPEG, AVI\n\n3. Cover Image (OPTIONAL - Usually Not Needed!):\n\n   DEFAULT BEHAVIOR (Recommended 95% of the time):\n   - Do NOT provide cover_image_url parameter\n   - TikTok automatically extracts a frame from your video as thumbnail\n   - This is the SAFEST option - aspect ratios always match!\n\n   ONLY provide cover_image_url if:\n   - User explicitly requests a specific custom thumbnail\n   - AND you've verified aspect ratios match (see validation below)\n\n   If user insists on custom cover:\n   - Ensure it's a direct image URL (not Google Drive share link!)\n   - Aspect ratio MUST match video exactly:\n     * Video 9:16 vertical - Cover 9:16 vertical (1080x1920)\n     * Video 16:9 horizontal - Cover 16:9 horizontal (1920x1080)\n   - If unsure: OMIT cover_image_url and explain TikTok will auto-extract\n\n4. Research the business:\n   - Understand products/services, target audience\n   - Review brand messaging and offers\n   - TikTok video best practices: hook viewers in first 3 seconds, authentic content, trending sounds/effects\n\n5. Craft compelling ad creative:\n   - Ad text: 12-100 chars (50 or less recommended for no truncation)\n   - Write for TikTok's young, mobile-first audience\n   - Use casual, conversational language\n   - Call-to-action: WATCH_NOW, LEARN_MORE, SHOP_NOW, etc.\n\n6. Define targeting:\n   - Locations: Default is USA, can specify others\n   - Age groups: TikTok core demographic is 18-34\n   - Gender: Unless product is gender-specific, use GENDER_UNLIMITED\n\nRequired Fields:\n1. campaign_name - Descriptive name (auto-suffixed with timestamp)\n2. budget_daily - Minimum $50/day for video ads (higher than image ads)\n3. video_url - Public video URL (must be validated first!)\n4. ad_text - Main message (12-100 chars, 50 recommended)\n5. landing_page_url - HTTPS URL where users land\n\nOptional Fields:\n- call_to_action - CTA button (default: WATCH_NOW)\n\n- cover_image_url - Custom video thumbnail (OPTIONAL - Not Recommended!)\n\n  IMPORTANT: This is OPTIONAL!\n  - Default (omit this field): TikTok auto-extracts frame from video - RECOMMENDED\n  - Only provide if user explicitly wants custom thumbnail AND aspect ratios match\n\n  If you decide to provide cover_image_url:\n  - Aspect ratio MUST exactly match video or TikTok will reject\n  - Video 9:16 vertical - Cover 9:16 vertical (1080x1920)\n  - Video 16:9 horizontal - Cover 16:9 horizontal (1920x1080)\n  - When in doubt: OMIT this field - TikTok auto-extraction always works!\n\n- target_locations - List of TikTok location IDs (default: USA)\n- target_age_groups - Age ranges to target\n- target_gender - Gender targeting (default: all)\n- advertiser_id - TikTok advertiser account\n\nWhat happens when you call this tool:\n1. Creates Campaign with TRAFFIC objective\n2. Creates Ad Group with PLACEMENT_TYPE_NORMAL (TikTok feed only)\n3. Downloads video from URL and uploads to TikTok (UPLOAD_BY_URL with unique filename)\n4. Uploads cover image (custom or auto-extracted from video)\n5. Creates Ad with video + cover creative\n6. Campaign goes ACTIVE immediately\n7. TikTok processes video (5-15 minutes) and reviews ad creative (24 hours)\n\nIdempotency Support:\n- If creation fails partway, partial state is logged (campaign_id, ad_group_id, video_id)\n- You can retry with same parameters - system will resume from checkpoint\n- Prevents duplicate campaigns and video uploads\n\nExecution time: 30-60 seconds (TikTok video upload takes longer than images)\n\nCommon Errors & How YOU Should Handle:\n\n1. \"text/html\" Content-Type from validation:\n   - Means URL is a web page, not direct video download\n   - Common with: Google Drive share links, YouTube embeds\n   - Your Action: Convert Google Drive links OR ask user for direct download URL\n\n2. \"Aspect ratio mismatch\" error:\n   - Cover image doesn't match video dimensions\n   - Your Action: OMIT cover_image_url, tell user TikTok will auto-extract\n\n3. \"Video upload failed\":\n   - Usually means video URL became inaccessible or wrong format\n   - Your Action: Re-validate URL, ensure it's direct download link\n\n4. \"Authorization failed\":\n   - User doesn't have TikTok connected or quota exceeded\n   - Your Action: Guide user to connect TikTok account first\n\nBest Practices:\n- Start with $50-100/day budget for video (higher than images due to CPV)\n- Test multiple video variations (different hooks, CTAs)\n- Monitor video completion rate - TikTok rewards engaging videos with lower costs\n- First 3 seconds are critical - hook viewers immediately\n- Use vertical 9:16 format for best mobile experience\n- Add captions - 85% of TikTok is watched with sound off\n- Learning phase is 7 days - let algorithm optimize before making changes",
        "operationId": "execute_create_tiktok_video_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_text": "string",
                  "app_id": "string",
                  "app_promotion_type": "string",
                  "budget_daily": 1.0,
                  "budget_lifetime": 1.0,
                  "campaign_name": "string",
                  "landing_page_url": "https://example.com",
                  "objective": "TRAFFIC",
                  "schedule_end_time": "string",
                  "video_url": "https://example.com"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for creating TikTok In-Feed video ad campaigns",
                    "properties": {
                      "ad_text": {
                        "description": "Ad text (1-100 characters). Recommended: 50 characters or less.",
                        "maxLength": 100,
                        "minLength": 1,
                        "title": "Ad Text",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "app_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok app ID for APP_PROMOTION campaigns. Required when objective is APP_PROMOTION.",
                        "title": "App Id"
                      },
                      "app_promotion_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "App promotion type: 'APP_INSTALL' or 'APP_RETARGETING'. Default: APP_INSTALL.",
                        "title": "App Promotion Type"
                      },
                      "audience_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom audience IDs to include.",
                        "title": "Audience Ids"
                      },
                      "budget_daily": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Daily budget in account currency. Mutually exclusive with budget_lifetime.",
                        "title": "Budget Daily"
                      },
                      "budget_lifetime": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Lifetime budget in account currency. Requires schedule_end_time. Mutually exclusive with budget_daily.",
                        "title": "Budget Lifetime"
                      },
                      "budget_optimize_on": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Budget Optimize On"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Call To Action"
                      },
                      "campaign_name": {
                        "description": "Campaign name (will be automatically suffixed with timestamp for uniqueness)",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "card_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel card ID for multi-card ads. Create carousel cards first in TikTok Ads Manager.",
                        "title": "Card Id"
                      },
                      "card_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Carousel card type: 'IMAGE' or 'PRODUCT'. Required when card_id is provided.",
                        "title": "Card Type"
                      },
                      "comment_disabled": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Disable comments on ads. Default: false (comments allowed).",
                        "title": "Comment Disabled"
                      },
                      "cover_image_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom cover image URL (9:16, 1080x1920). Auto-generated if not provided.",
                        "title": "Cover Image Url"
                      },
                      "display_name": {
                        "anyOf": [
                          {
                            "maxLength": 40,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Brand/business name displayed on the ad (max 40 characters). If not provided, uses the campaign name.",
                        "title": "Display Name"
                      },
                      "excluded_audience_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Custom audience IDs to exclude.",
                        "title": "Excluded Audience Ids"
                      },
                      "interest_category_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Interest category IDs for targeting.",
                        "title": "Interest Category Ids"
                      },
                      "landing_page_url": {
                        "description": "Landing page URL (must be HTTPS).",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Language codes (e.g., ['en', 'es']).",
                        "title": "Languages"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "TRAFFIC",
                        "description": "Campaign objective. Options: 'TRAFFIC', 'CONVERSIONS', 'LEAD_GENERATION', 'REACH', 'VIDEO_VIEWS', 'APP_PROMOTION' (requires app_id). Default: TRAFFIC",
                        "title": "Objective"
                      },
                      "operating_systems": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target device OS. Options: 'ANDROID', 'IOS'. Default: all.",
                        "title": "Operating Systems"
                      },
                      "optimization_event": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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",
                        "title": "Optimization Event"
                      },
                      "pixel_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok Pixel ID for conversion tracking. Required for CONVERSIONS objective.",
                        "title": "Pixel Id"
                      },
                      "placement_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "'PLACEMENT_TYPE_AUTOMATIC' (default) or 'PLACEMENT_TYPE_NORMAL' (manual).",
                        "title": "Placement Type"
                      },
                      "placements": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Manual placements: 'PLACEMENT_TIKTOK', 'PLACEMENT_PANGLE', 'PLACEMENT_GLOBAL_APP_BUNDLE'.",
                        "title": "Placements"
                      },
                      "schedule_end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign end time 'YYYY-MM-DD HH:MM:SS'. Required for budget_lifetime.",
                        "title": "Schedule End Time"
                      },
                      "target_age_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Age groups: 'AGE_13_17', 'AGE_18_24', 'AGE_25_34', 'AGE_35_44', 'AGE_45_54', 'AGE_55_100'.",
                        "title": "Target Age Groups"
                      },
                      "target_gender": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "GENDER_UNLIMITED",
                        "description": "Gender: 'GENDER_UNLIMITED', 'GENDER_MALE', 'GENDER_FEMALE'.",
                        "title": "Target Gender"
                      },
                      "target_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok location IDs. Default: ['6252001'] (US). UK=2635167, Canada=6251999, Australia=2077456.",
                        "title": "Target Locations"
                      },
                      "tiktok_item_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Tiktok Item Id"
                      },
                      "video_download_disabled": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Disable video download on TikTok. Default: false (users can download).",
                        "title": "Video Download Disabled"
                      },
                      "video_url": {
                        "description": "Public video URL (from Google Drive, Vimeo, Dropbox, etc.).",
                        "title": "Video Url",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "video_url",
                      "ad_text",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_tiktok_video_campaign)"
                  },
                  "success": true,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_tiktok_video_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_tiktok_video_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a TikTok ad campaign with a VIDEO (not images) [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_tiktok_video_campaign"
      }
    },
    "/api/v1/tools/create_youtube_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\n\ud83d\udd04 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 \u2192 commit.\n\n\u26a0\ufe0f CRITICAL WARNING \u26a0\ufe0f\n- Call this tool ONLY ONCE per campaign\n- Creates REAL campaigns that cost REAL money\n- Do NOT retry automatically if errors occur\n- Report errors to user instead of retrying\n\n\u26a0\ufe0f CRITICAL PREREQUISITE:\n- MUST validate the YouTube video first using validate_video tool\n- Video must be public or unlisted on YouTube (NOT private)\n\n\ud83c\udfac **YouTube Campaign Placements:**\n\u2705 YouTube In-Feed (appears in search results & related videos)\n\u2705 YouTube In-Stream (plays before/during/after videos)\n\u2705 YouTube Shorts (appears in Shorts feed)\n\u274c Gmail (disabled for YouTube campaigns)\n\u274c Discover (disabled for YouTube campaigns)\n\u274c Display (disabled for YouTube campaigns)\n\n\ud83d\udccb **YOUR CRITICAL ROLE: Campaign Strategist & Text Creator**\n\n**STEP 1: Validate Video First**\n\nBefore calling this tool:\n1. Get user's YouTube video URL or ID\n2. Call validate_video tool with platform=\"pmax\" to verify it\n3. Confirm video is accessible (public or unlisted)\n\n\ud83d\udd34 **STEP 1.5: MANDATORY \u2014 Discover Existing Assets**\nBEFORE proceeding, you MUST call discover_existing_assets to check for existing image/logo assets:\n1. Call: discover_existing_assets(asset_types=[\"image\", \"logo\"], target_domain=\"<user's domain>\")\n2. Present findings to the user:\n   - Show all available logos and images with dimensions\n   - Show count of assets found per type\n3. Ask the user:\n   \"I found [N] existing assets in your account. Would you like to:\n   A) Reuse an existing logo (saves time, consistent branding)\n   B) Upload a new logo image\n   C) Upload a new logo from URL\"\n4. Based on answer:\n   - Reuse \u2192 Use logo_asset_id from discovered assets\n   - Upload new \u2192 Use validate_and_prepare_assets with logos_square \u2192 asset_bundle_id\n5. \ud83d\udeab NEVER skip this step \u2014 even if the user didn't mention assets\n\n**STEP 2: Collect Campaign Details from User**\n\nYOU MUST collect these from the user (ask if not provided):\n\n**REQUIRED Fields:**\n1. **Campaign Name** - Descriptive and unique\n2. **Daily Budget** - Minimum $15/day (recommended $50+)\n3. **YouTube Video ID** - 11-character video ID (validated in Step 1)\n4. **Final URL** - Landing page (must match verified domain)\n5. **Business Name** - Max 25 characters\n\n**STEP 3: Generate High-Quality Ad Copy**\n\n**\ud83d\udea8 BEFORE GENERATING TEXT - READ CHARACTER LIMITS \ud83d\udea8**\n\n**Headlines (1-5 required):**\n- **STRICT LIMIT: 40 characters maximum per headline**\n- Count characters BEFORE calling this tool!\n- Examples:\n  - \"Shop Premium Watches Today\" (26 chars) \u2705\n  - \"Free Worldwide Shipping Now\" (27 chars) \u2705\n\n**Descriptions (1-5 required):**\n- **STRICT LIMIT: 90 characters maximum per description**\n- Count characters BEFORE calling this tool!\n- Examples:\n  - \"Shop authentic luxury timepieces with expert curation and free worldwide shipping.\" (82 chars) \u2705\n\n**Long Headlines (optional, 1-5):**\n- **STRICT LIMIT: 90 characters maximum per long headline**\n- Falls back to regular headlines if not provided\n\n**\u26a0\ufe0f WILL BE REJECTED IF YOU EXCEED LIMITS - NO RETRIES!**\n\n**LOGO (REQUIRED - one of two options):**\n- **Option A** (existing logo): logo_asset_id from discover_existing_assets \u2014 use this if the account already has logo images\n- **Option B** (new upload): asset_bundle_id from validate_and_prepare_assets \u2014 use this if the account has NO existing logos (e.g., Search-only accounts)\n\n**Workflow:**\n1. First try discover_existing_assets to find existing logos\n2. If logos found \u2192 use logo_asset_id\n3. If NO logos found \u2192 ask user for a logo image URL, run validate_and_prepare_assets with logos_square, then use the returned asset_bundle_id\n\n**OPTIONAL Fields (use defaults if not provided):**\n- 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\n- target_locations: Defaults to [\"United States\"]\n- target_languages: Defaults to [\"en\"]\n- additional_video_ids: Up to 4 more videos (5 total max)\n- bidding_strategy: MAXIMIZE_CLICKS (default), MAXIMIZE_CONVERSIONS (needs conversion tracking), TARGET_CPA\n- target_cpa: Required only for TARGET_CPA bidding\n\n**STEP 4: Call create_youtube_campaign**\n\nAfter you have ALL details:\n- Validate character counts yourself BEFORE calling\n- Call create_youtube_campaign with complete payload\n- Wait for response (may take 10-20 seconds)\n\n**STEP 5: Handle Response**\n\n**If SUCCESS:**\n- Show campaign ID, name, budget, status\n- Explain YouTube-only placements (In-Feed, In-Stream, Shorts)\n- Explain campaign starts PAUSED for safety\n- Provide next steps (review, activate, monitor)\n\n**If FAILURE:**\n- Show error message clearly\n- Do NOT retry automatically\n- Guide user to fix the issue\n\n**Example Conversation Flow:**\n\nUser: \"I want to run YouTube ads for my product\"\n\nYou: \"I'd be happy to help create a YouTube video campaign! First, can you share your YouTube video URL so I can validate it?\"\n\n[User provides: https://youtu.be/eIZtladpm6c]\n\n[Call validate_video with video_url_or_id=\"eIZtladpm6c\", platform=\"pmax\"]\n\nYou: \"Your video is validated and ready! Now I need a few details:\n1. Campaign name?\n2. Daily budget? (minimum $15, recommended $50+)\n3. Landing page URL?\n4. Business name?\n\nI'll create compelling headlines and descriptions for your YouTube ads.\"\n\n[User provides details]\n\n[Call create_youtube_campaign with all details]\n\n**Video Specifications:**\n- Must be on YouTube (public or unlisted, NOT private)\n- Minimum 10 seconds duration recommended\n- Maximum 5 videos per campaign (1 primary + 4 additional)\n- Validated via validate_video tool before campaign creation\n\n**Bidding Options:**\n- MAXIMIZE_CLICKS: Best for most campaigns (default, works without conversion tracking)\n- MAXIMIZE_CONVERSIONS: Best when conversion tracking is set up on the account\n- TARGET_CPA: Best when you know your target cost per acquisition (requires conversion tracking)\n\n**Campaign Creation Best Practices:**\n1. Always validate video first with validate_video tool\n2. \ud83d\udd34 MANDATORY: Run discover_existing_assets to find logos/images \u2014 present results to user, ask if they want to reuse or upload new\n3. If reuse \u2192 use logo_asset_id; if upload new \u2192 validate_and_prepare_assets \u2192 asset_bundle_id\n4. Confirm budget with user ($15/day minimum)\n5. Create compelling, benefit-focused ad copy\n6. Campaign starts PAUSED - explain this to user\n7. This costs real money - be transparent\n8. \ud83d\udd34 AFTER campaign creation: Add extensions (sitelinks, callouts, structured snippets) \u2014 see STEP 6 below\n\n**STEP 6: MANDATORY \u2014 Add Extensions After Creation**\nAfter the campaign is successfully created, you MUST:\n1. Crawl the user's website to gather relevant page links, features, and service categories\n2. Add sitelinks: at least 4 links to key pages (add_sitelinks tool)\n3. Add callout extensions: 4-6 business highlights (add_callout_extensions tool)\n4. Add structured snippets: categorized features (add_structured_snippets tool)\n5. Verify with list_campaign_extensions to confirm all extensions are attached\nExtensions are FREE and increase ad visibility by 15-25%. NEVER skip this step.\n\n**Execution Time:** 10-20 seconds (direct backend API call)\n\n**Authentication:** Required (MCP OAuth 2.1)\n\n**CRITICAL REMINDERS:**\n- Campaign starts PAUSED for user safety\n- This costs real money - be transparent with user\n- Never retry on failure - report error to user\n- Always validate video BEFORE creating campaign",
        "operationId": "execute_create_youtube_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "additional_video_ids": [
                    "string"
                  ],
                  "bidding_strategy": "MAXIMIZE_CLICKS",
                  "budget_daily": 1.0,
                  "business_name": "string",
                  "call_to_action": "LEARN_MORE",
                  "campaign_name": "string",
                  "descriptions": [
                    "string"
                  ],
                  "final_url": "https://example.com",
                  "headlines": [
                    "string"
                  ],
                  "long_headlines": [
                    "string"
                  ],
                  "target_languages": [
                    "string"
                  ],
                  "target_locations": [
                    "string"
                  ],
                  "youtube_video_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for create_youtube_campaign tool.\n\nCreates a YouTube Video campaign using Google Ads Demand Gen format\nwith YouTube-only channel controls (In-Feed, In-Stream, Shorts).\nGmail, Discover, and Display placements are disabled.",
                    "properties": {
                      "additional_video_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Up to 4 additional YouTube video IDs (max 5 total including primary). Each must be exactly 11 characters. Example: ['abc123xyz00', 'def456uvw00']",
                        "title": "Additional Video Ids"
                      },
                      "asset_bundle_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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-8bfb-3173f6de9622'",
                        "title": "Asset Bundle Id"
                      },
                      "audience_segments": {
                        "anyOf": [
                          {
                            "additionalProperties": true,
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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, skip audience_segments entirely. Keys: 'in_market_audience_ids' (List[int] -- people actively researching), 'affinity_audience_ids' (List[int] -- people with long-term interests), 'custom_audience_ids' (List[str] -- custom audience resource names from search_audiences), 'user_list_ids' (List[str] -- remarketing/customer match resource names). Example: {'in_market_audience_ids': [80463, 80520], 'affinity_audience_ids': [92913]}",
                        "title": "Audience Segments"
                      },
                      "bidding_strategy": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "MAXIMIZE_CLICKS",
                        "description": "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.",
                        "title": "Bidding Strategy"
                      },
                      "budget_daily": {
                        "description": "Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies \u2014 pass the user's amount as-is. Example: if user says '\u20b91000/day', pass 1000 (not a USD conversion). The Google Ads API interprets this in the account's currency automatically. Google recommends minimum ~$20/day USD equivalent for YouTube/Demand Gen campaigns.",
                        "minimum": 1.0,
                        "title": "Budget Daily",
                        "type": "number"
                      },
                      "business_name": {
                        "description": "Business name, max 25 characters. Example: 'Luxury Watch Co'",
                        "maxLength": 25,
                        "title": "Business Name",
                        "type": "string"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEARN_MORE",
                        "description": "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",
                        "title": "Call To Action"
                      },
                      "campaign_name": {
                        "description": "Campaign name (e.g., 'YouTube Summer Promo 2025')",
                        "title": "Campaign Name",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "description": "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 over 90 will be REJECTED!",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 5,
                        "minItems": 1,
                        "title": "Descriptions",
                        "type": "array"
                      },
                      "final_url": {
                        "description": "Landing page URL (must match verified domain). Example: 'https://example.com/product'",
                        "title": "Final Url",
                        "type": "string"
                      },
                      "headlines": {
                        "description": "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!",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 5,
                        "minItems": 1,
                        "title": "Headlines",
                        "type": "array"
                      },
                      "logo_asset_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Logo image asset ID (1:1 square aspect ratio) from discover_existing_assets. Either this OR asset_bundle_id is required. Example: '123456789'",
                        "title": "Logo Asset Id"
                      },
                      "long_headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "maxItems": 5,
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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 over 90 will be REJECTED!",
                        "title": "Long Headlines"
                      },
                      "target_cpa": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target CPA in USD (only required when bidding_strategy='TARGET_CPA'). Example: 25.0 means target $25 per conversion.",
                        "title": "Target Cpa"
                      },
                      "target_languages": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Language targets as ISO codes (e.g., ['en', 'es']). Defaults to English.",
                        "title": "Target Languages"
                      },
                      "target_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Target Locations"
                      },
                      "youtube_video_id": {
                        "description": "Primary YouTube video ID (exactly 11 characters). Must be validated first using validate_video tool. Example: 'dQw4w9WgXcQ'",
                        "title": "Youtube Video Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_name",
                      "budget_daily",
                      "youtube_video_id",
                      "final_url",
                      "business_name",
                      "headlines",
                      "descriptions"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for create_youtube_campaign)"
                  },
                  "success": true,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: create_youtube_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "create_youtube_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "create_youtube_campaign"
      }
    },
    "/api/v1/tools/delete_linkedin_creative/execute": {
      "post": {
        "description": "User wants to delete, archive, or remove a LinkedIn ad/creative.\n\nArchive or permanently delete a LinkedIn creative.\n\nParameters:\n- creative_id: Creative ID to archive or delete (required)\n- account_id: Optional LinkedIn Ad Account ID\n- permanent: If True, permanently deletes (irreversible). If False, archives (default, recoverable).\n\nWhat happens:\n- Default (permanent=False): Creative is archived and can be restored later\n- permanent=True: Creative is permanently deleted and cannot be recovered\n\nExample Prompts:\n- \"Delete creative 12345\"\n- \"Archive my LinkedIn ad\"\n- \"Remove creative from my campaign\"\n- \"Permanently delete ad 12345\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_delete_linkedin_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "creative_id": "string",
                  "permanent": false
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for archiving/deleting a creative",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "creative_id": {
                        "description": "Creative ID to archive or delete",
                        "title": "Creative Id",
                        "type": "string"
                      },
                      "permanent": {
                        "default": false,
                        "description": "If True, permanently deletes. If False, archives (recoverable).",
                        "title": "Permanent",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "creative_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for delete_linkedin_creative)"
                  },
                  "success": true,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: delete_linkedin_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to delete, archive, or remove a LinkedIn ad/creative [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "delete_linkedin_creative"
      }
    },
    "/api/v1/tools/delete_monitor/execute": {
      "post": {
        "description": "Delete a monitoring alert by its task ID.\n\n**When to use:**\n- \"Delete my ROAS monitor\"\n- \"Remove this alert\"\n- \"Stop monitoring my campaign\"\n\nGet the task_id from `list_monitors` first.\n\nAccepts `task_id`, `alert_id`, or `monitor_id` \u2014 all treated the same.",
        "operationId": "execute_delete_monitor",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "task_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "task_id": {
                        "description": "ID of the monitor to delete. Get from list_monitors.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "task_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for delete_monitor)"
                  },
                  "success": true,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: delete_monitor",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "delete_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Delete a monitoring alert by its task ID [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "delete_monitor"
      }
    },
    "/api/v1/tools/detect_meta_creative_fatigue/execute": {
      "post": {
        "description": "User asks about creative fatigue, ad refresh timing, frequency management, declining CTR, when to replace ads, or audience exhaustion on Meta/Facebook/Instagram.\n\nThis tool analyzes all Meta Ads creatives for fatigue indicators using frequency-CTR correlation analysis and provides a fatigue score (0-100) for each ad.\n\nReturns:\n- Fatigue score (0-100) for each ad based on weighted factors\n- Severely fatigued ads (score \u226580) - immediate action required\n- At-risk ads (score 50-79) - plan refresh within 7-14 days\n- Healthy ads count\n- Daily spend being wasted on fatigued creatives\n- Projected monthly waste\n- Refresh schedule recommendations\n- Contributing factors for each fatigued ad\n\nWhen to use this tool:\n- \"Are any of my Meta/Facebook/Instagram ads fatigued?\"\n- \"When should I refresh my creatives?\"\n- \"Why is my CTR declining?\"\n- \"Which ads have high frequency?\"\n- \"How much am I wasting on fatigued ads?\"\n- \"What's my ad refresh schedule?\"\n- \"Are my retargeting ads showing too often?\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- frequency_threshold_cold: 2.0-8.0 (default: 4.0) - threshold for cold traffic audiences\n- frequency_threshold_retargeting: 4.0-10.0 (default: 7.0) - threshold for retargeting audiences\n- ctr_decline_threshold: 0.10-0.50 (default: 0.20 = 20%) - CTR decline to flag as fatigued\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 2-5 seconds (cached database query with analysis)\nData source: meta_ad_creative_metrics table (ad-level daily metrics with frequency, CTR, video completion)\n\nFatigue Score Calculation:\nThe fatigue score (0-100) is calculated using weighted factors:\n- Frequency (40%): impressions/reach ratio vs threshold\n- CTR Decline (35%): % drop in CTR vs previous period\n- Days Running (25%): creative age vs optimal refresh timing\n\nSeverity Levels:\n- \ud83d\udd34 Score \u226580: Severely fatigued - PAUSE immediately and replace\n- \ud83d\udfe1 Score 50-79: At risk - prepare replacement within 7-14 days\n- \u2705 Score <50: Healthy - continue monitoring\n\nBest practices:\n- Cold traffic: Refresh creatives when frequency reaches 3-4x\n- Retargeting: Can tolerate higher frequency (up to 6-7x)\n- Create 3-5 variations per ad set for automatic rotation\n- Monitor weekly for frequency and CTR trends",
        "operationId": "execute_detect_meta_creative_fatigue",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "frequency_threshold_cold": 4.0,
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta creative fatigue detection analysis",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "ctr_decline_threshold": {
                        "default": 0.2,
                        "description": "CTR decline threshold to flag as fatigued (0.10-0.50 = 10%-50%). Default is 0.20 (20% decline).",
                        "title": "Ctr Decline Threshold",
                        "type": "number"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "frequency_threshold_cold": {
                        "default": 4.0,
                        "description": "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.",
                        "title": "Frequency Threshold Cold",
                        "type": "number"
                      },
                      "frequency_threshold_retargeting": {
                        "default": 7.0,
                        "description": "Frequency threshold for retargeting audiences (4.0-10.0). Default is 7.0 - retargeting audiences can tolerate higher frequency.",
                        "title": "Frequency Threshold Retargeting",
                        "type": "number"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for detect_meta_creative_fatigue)"
                  },
                  "success": true,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: detect_meta_creative_fatigue",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_meta_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about creative fatigue, ad refresh timing, frequency management, declining CTR, when to replace ads, or aud...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "detect_meta_creative_fatigue"
      }
    },
    "/api/v1/tools/detect_tiktok_creative_fatigue/execute": {
      "post": {
        "description": "Detect TikTok creative fatigue using video-specific metrics (hook rate decline, completion rate decline, engagement decline, frequency).\n\nReturns: fatigue score (0-100) per ad, severely fatigued / at-risk / healthy counts, refresh schedule, recommendations.\n\nTikTok-specific: Hook rate (first 2 seconds) is the primary fatigue signal (35% weight).\n\nWhen to use: \"Are my TikTok ads fatigued?\", \"TikTok creative refresh\", \"Which TikTok videos need replacing?\"\n\nParameters:\n- lookback_days: default 30\n- frequency_threshold: override (default 3.5)",
        "operationId": "execute_detect_tiktok_creative_fatigue",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "end_date": "string",
                  "frequency_threshold": 1.0,
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD",
                        "title": "End Date"
                      },
                      "frequency_threshold": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override frequency threshold (default 3.5)",
                        "title": "Frequency Threshold"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for detect_tiktok_creative_fatigue)"
                  },
                  "success": true,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: detect_tiktok_creative_fatigue",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "detect_tiktok_creative_fatigue"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Detect TikTok creative fatigue using video-specific metrics (hook rate decline, completion rate decline, engagement d...",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "detect_tiktok_creative_fatigue"
      }
    },
    "/api/v1/tools/discover_existing_assets/execute": {
      "post": {
        "description": "\ud83d\udd0d Discover existing assets in the Google Ads account (images, sitelinks, callouts, structured snippets).\n\n**Phase 0: Asset Discovery** - Read-only tool, safe to call anytime.\n\n\ud83d\udd34 **MANDATORY WORKFLOW - ALWAYS DO THIS FIRST:**\n\nBEFORE creating ANY Performance Max campaign, you MUST:\n1. Call this tool: discover_existing_assets(target_domain=\"<user's domain>\")\n2. Show the user what was found (images, sitelinks)\n3. Ask: \"Would you like to reuse these assets or upload new ones?\"\n4. Based on answer:\n   - Reuse \u2192 Use existing_image_ids in create_pmax_campaign\n   - Upload new \u2192 Use validate_and_prepare_assets \u2192 asset_bundle_id\n   - Mix both \u2192 Use both parameters (hybrid mode)\n\n\ud83d\udeab **NEVER create PMAX without discovering assets first** (unless user explicitly says \"skip discovery, upload new\")\n\n**What This Tool Does:**\n- Queries Google Ads Asset Library for existing validated assets\n- Uses SMART 2-TIER FILTERING to show only reliable assets\n- Filters out invalid/test data automatically\n- Returns assets that were successfully attached to previous campaigns\n\n**2-Tier Smart Discovery:**\n\n**Tier 1: Campaign-Validated Assets (High Confidence)** \u2705\n- Assets currently attached to ENABLED, PAUSED, or REMOVED campaigns\n- These have been validated by Google and won't fail\n- Includes domain filtering for sitelinks\n- Best option for reuse\n\n**Tier 2: No Validated Assets (Ask User)** \u26a0\ufe0f\n- Account has no suitable assets in campaigns\n- Recommends uploading fresh assets or website research\n- Safer than using orphaned assets from library\n\n**Parameters:**\n- target_domain: Optional domain to filter sitelinks (e.g., \"rooterhero.com\")\n  - **CRITICAL for multi-domain accounts!** Google rejects wrong-domain sitelinks\n  - **Extract the base domain** from campaign URL (just the domain part)\n  - Examples: \"https://www.rooterhero.com/services\" \u2192 \"rooterhero.com\", \"sahaayak.life\" \u2192 \"sahaayak.life\"\n  - Only sitelinks matching this domain will be returned\n  - Prevents Google API errors for domain mismatches\n\n**Returns:**\n- images: List of validated images (landscape, square, portrait, logos)\n- sitelinks: List of validated sitelinks (filtered by domain if provided)\n- callouts: List of validated callouts\n- structured_snippets: List of validated structured snippets\n- recommendation: Smart guidance on whether to reuse or upload new\n\n**Example ChatGPT Flow:**\n\nUser: \"Create a campaign for rooterhero.com\" (or \"https://www.rooterhero.com/services\")\n\nStep 1: Extract base domain \u2192 \"rooterhero.com\"\nStep 2: Call discover_existing_assets(target_domain=\"rooterhero.com\")\n\nIf Tier 1 (validated assets found):\n  ChatGPT: \"I found 8 sitelinks and 11 images from your previous Rooter Hero campaigns.\n            These were validated by Google. Would you like to reuse them, or upload fresh assets?\"\n\n  User: \"Reuse them\" \u2192 Use those assets in Phase 1\n  User: \"Upload new\" \u2192 Follow current upload flow\n\nIf Tier 2 (no validated assets):\n  ChatGPT: \"No validated assets found for rooterhero.com in your campaigns.\n            Options:\n            1. Upload fresh images/sitelinks\n            2. Let me research rooterhero.com and suggest sitelinks\n            3. Create campaign without extensions\"\n\n**When to Call:**\n- BEFORE creating Search campaigns (to discover sitelinks, callouts, snippets)\n- BEFORE creating PMAX campaigns (to discover images)\n- When user asks \"What assets do I have?\"\n- When user wants to reuse existing assets\n\n**When NOT to Call:**\n- User explicitly says \"I'll upload new images\"\n- User wants to create minimal campaign without extensions\n\n**Execution Time:** 1-3 seconds (read-only query)\n\n**Authentication:** Required (MCP OAuth 2.1)\n\n**Note:** This is Phase 0 - discovery only. Phase 1 will enable reusing discovered assets in campaigns.",
        "operationId": "execute_discover_existing_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "asset_types": [
                    "images",
                    "sitelinks",
                    "callouts",
                    "structured_snippets"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "asset_types": {
                        "default": [
                          "images",
                          "sitelinks",
                          "callouts",
                          "structured_snippets"
                        ],
                        "description": "Asset types to discover. Default: all types.",
                        "items": {
                          "enum": [
                            "images",
                            "sitelinks",
                            "callouts",
                            "structured_snippets"
                          ],
                          "type": "string"
                        },
                        "type": "array"
                      },
                      "customer_id": {
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "type": "string"
                      },
                      "target_domain": {
                        "description": "Optional domain to filter sitelinks. **Extract the base domain from the campaign URL.** Examples: 'https://www.rooterhero.com/services' \u2192 'rooterhero.com', 'sahaayak.life' \u2192 'sahaayak.life'. Only sitelinks matching this domain will be returned. Critical: Google rejects wrong-domain sitelinks.",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for discover_existing_assets)"
                  },
                  "success": true,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: discover_existing_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_existing_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udd0d Discover existing assets in the Google Ads account (images, sitelinks, callouts, structured snippets)",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "discover_existing_assets"
      }
    },
    "/api/v1/tools/discover_linkedin_assets/execute": {
      "post": {
        "description": "User wants to find existing images/videos in their LinkedIn account to reuse.\n\nDiscover previously uploaded image and video assets for a LinkedIn Ad Account.\n\nIMPORTANT:\n- Call `get_linkedin_organizations` FIRST to get both organization_id AND account_id\n- The account_id is REQUIRED to find images uploaded via the new LinkedIn API\n\nWhat this tool does:\n- Queries LinkedIn for existing assets in the ad account\n- Returns images and videos with URNs, dimensions, and status\n- Assets can be reused in new campaigns without re-uploading\n\nParameters:\n- organization_id: LinkedIn Organization (Company Page) ID (required)\n- account_id: LinkedIn Ad Account (Sponsored Account) ID (REQUIRED for image discovery!)\n  - Get this from `get_linkedin_organizations` response\n  - Without this, images uploaded via new API won't be found\n- asset_type: Filter by type - 'image', 'video', or 'all' (default: all)\n- limit: Maximum assets to return (default: 50, max: 100)\n\nReturns:\n- List of images with asset_urn, dimensions, status\n- List of videos with asset_urn, dimensions, duration, status\n- Total count of assets found\n\nUse this tool to:\n- Find existing images to reuse (saves time and ensures consistency)\n- Check what video assets are available for Video Ad campaigns\n- Avoid uploading duplicate assets\n\nWorkflow:\n1. Call `get_linkedin_organizations` first - get organization_id AND account_id\n2. Call `discover_linkedin_assets` with both IDs\n3. Use returned `asset_urn` in `create_linkedin_image_campaign`\n\nExecution time: 3-5 seconds",
        "operationId": "execute_discover_linkedin_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "asset_type": "all",
                  "limit": 50,
                  "organization_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for discovering existing LinkedIn image and video assets",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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:sponsoredAccount:XXXXX).",
                        "title": "Account Id"
                      },
                      "asset_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "all",
                        "description": "Type of assets to discover: 'image', 'video', or 'all' (default). Use 'image' for Single Image Ads, 'video' for Video Ads.",
                        "title": "Asset Type"
                      },
                      "limit": {
                        "anyOf": [
                          {
                            "maximum": 100,
                            "minimum": 1,
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 50,
                        "description": "Maximum number of assets to return (default: 50, max: 100)",
                        "title": "Limit"
                      },
                      "organization_id": {
                        "description": "LinkedIn Organization (Company Page) ID. This is the organization that owns the assets. Use the organization ID from your connected LinkedIn account.",
                        "title": "Organization Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "organization_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for discover_linkedin_assets)"
                  },
                  "success": true,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: discover_linkedin_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to find existing images/videos in their LinkedIn account to reuse",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "discover_linkedin_assets"
      }
    },
    "/api/v1/tools/discover_meta_assets/execute": {
      "post": {
        "description": "User wants to browse existing images in their Meta Ad Library for reuse in new campaigns.\n\nThis tool retrieves existing images that have been uploaded to Meta's Ad Library, allowing users to reuse them in new campaigns without uploading again.\n\nReturns:\n- List of existing images with their hashes\n- Image dimensions and thumbnails\n- Created timestamps\n- Instructions for using images in campaigns\n\nWhen to use this tool:\n- \"Show me my existing Meta ad images\"\n- \"What images do I already have in Meta?\"\n- \"I want to reuse an existing image for my campaign\"\n- \"List my Facebook ad library images\"\n- Before uploading new images - check if they already exist\n\nParameters:\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n- limit: Max images to return (default 50, max 100)\n\nExecution time: 2-5 seconds\nData source: Meta Ad Library API (live)\n\nWorkflow:\n1. Use `discover_meta_assets` to find existing images\n2. Copy the `image_hash` from an image you want to use\n3. Use that hash with `create_meta_image_campaign` via the `existing_image_hash` parameter",
        "operationId": "execute_discover_meta_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "limit": 50
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for discovering existing Meta ad images",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "limit": {
                        "default": 50,
                        "description": "Maximum number of assets to return (default 50, max 100)",
                        "title": "Limit",
                        "type": "integer"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for discover_meta_assets)"
                  },
                  "success": true,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: discover_meta_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to browse existing images in their Meta Ad Library for reuse in new campaigns",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "discover_meta_assets"
      }
    },
    "/api/v1/tools/discover_tiktok_assets/execute": {
      "post": {
        "description": "User wants to reuse existing TikTok images instead of uploading new ones.\n\nBrowse existing images in TikTok Asset Library for reuse in campaigns.\n\nIMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.\n\nReturns:\n- List of all images in your TikTok Asset Library\n- Image IDs, dimensions, file sizes, upload dates\n- Usage instructions for reusing assets\n\nParameters:\n- advertiser_id: Optional (uses connected account if omitted)\n\nExecution time: 2-5 seconds (direct backend API call)\n\nUse this tool to:\n- Find existing images you can reuse in new campaigns\n- Avoid re-uploading the same images\n- See what assets are available in your library\n\nAfter calling this tool, you can use the returned image IDs in `create_tiktok_campaign` via the `existing_image_ids` parameter.",
        "operationId": "execute_discover_tiktok_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID (optional - will use primary account if not provided)",
                        "title": "Advertiser Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for discover_tiktok_assets)"
                  },
                  "success": true,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: discover_tiktok_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "discover_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to reuse existing TikTok images instead of uploading new ones",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "discover_tiktok_assets"
      }
    },
    "/api/v1/tools/duplicate_meta_campaign/execute": {
      "post": {
        "description": "User wants to duplicate/copy an existing Meta campaign with all its ad sets, ads, and settings.\n\nIMPORTANT: This creates a NEW real campaign in Meta Ads. The duplicate starts in PAUSED status by default for review.\n\nThis tool copies the entire campaign structure including:\n- Campaign settings (objective, budget strategy)\n- All ad sets (targeting, budgets, schedules)\n- All ads (creatives, text, CTAs)\n\nReturns:\n- Original and new campaign IDs\n- New campaign status\n- Ads Manager URL for the new campaign\n- Next steps for review and activation\n\nWhen to use this tool:\n- \"Duplicate my campaign\"\n- \"Copy campaign [ID]\"\n- \"Create a copy of this campaign\"\n- \"I want to A/B test with a copy of my campaign\"\n- \"Clone my campaign with different targeting\"\n\nParameters:\n- campaign_id: The Meta Campaign ID to duplicate (required)\n- new_name: Name for the new campaign (optional, defaults to original + \" - Copy\")\n- status: Status for new campaign \u2014 'PAUSED' (default, recommended) or 'ACTIVE'\n\nExecution time: 10-30 seconds (depends on campaign size)\nCreates: New real campaign in Meta Ads\n\nCommon use cases:\n1. A/B Testing: Duplicate, then modify targeting or creative on the copy\n2. Seasonal variants: Copy a proven campaign, update copy and dates\n3. New market expansion: Duplicate, change location targeting\n4. Budget testing: Copy, change budget levels\n\nWorkflow:\n1. Use `list_meta_campaigns` to find the campaign ID\n2. Use `duplicate_meta_campaign` to create the copy\n3. Use `update_meta_campaign` or `update_meta_ad_set` to modify the copy\n4. Use `resume_meta_campaign` when ready to launch",
        "operationId": "execute_duplicate_meta_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "new_name": "string",
                  "status": "PAUSED"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for duplicating a Meta campaign",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "description": "The Meta Campaign ID to duplicate (required)",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "new_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Name for the new campaign (optional - defaults to original name + ' - Copy')",
                        "title": "New Name"
                      },
                      "status": {
                        "default": "PAUSED",
                        "description": "Status for the new campaign: 'PAUSED' (default, recommended for review) or 'ACTIVE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for duplicate_meta_campaign)"
                  },
                  "success": true,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: duplicate_meta_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "duplicate_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to duplicate/copy an existing Meta campaign with all its ad sets, ads, and settings [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "duplicate_meta_campaign"
      }
    },
    "/api/v1/tools/explain_linkedin_anomaly/execute": {
      "post": {
        "description": "User asks why their LinkedIn metrics changed,\nwants to understand a performance drop/spike, or says something like\n\"My LinkedIn leads dropped 40%, why?\"\n\nExplains significant metric changes with contributing factors:\n\nLinkedIn-Specific Factors Analyzed:\n- CPM changes (auction dynamics, competition)\n- CTR changes (creative effectiveness)\n- Audience saturation (B2B pools are smaller)\n- Seniority targeting shifts\n- Industry performance shifts\n- Company size targeting changes\n- Campaign changes (paused, new, budget)\n- B2B seasonal patterns (holidays, fiscal quarters)\n- Creative fatigue (14-day threshold)\n\nReturns:\n- Metric change summary (current vs previous)\n- Severity assessment (CRITICAL, HIGH, MEDIUM, LOW)\n- Contributing factors ranked by impact\n- Historical context (30/60/90 day averages)\n- Similar periods for comparison\n- Actionable recommendations\n- LinkedIn B2B-specific insights\n\nParameters:\n- metric: Metric to analyze (required). Options:\n  roas, ctr, cpc, cpm, conversions, conversion_rate,\n  leads, engagement_rate, lead_form_completion_rate\n- period_start: Start date (YYYY-MM-DD format, required)\n- period_end: End date (YYYY-MM-DD format, required)\n- comparison_period_start: Optional comparison start date\n- comparison_period_end: Optional comparison end date\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"Why did my LinkedIn ROAS drop last week?\"\n- \"Explain the LinkedIn CTR decline in December\"\n- \"My LinkedIn leads dropped 40%, what happened?\"\n- \"Why did LinkedIn CPC increase?\"\n\nExecution time: 4-6 seconds",
        "operationId": "execute_explain_linkedin_anomaly",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "comparison_period_end": "string",
                  "comparison_period_start": "string",
                  "metric": "ctr",
                  "period_end": "string",
                  "period_start": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for explaining LinkedIn metric anomalies",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "comparison_period_end": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional end date of comparison period. Defaults to day before period_start.",
                        "title": "Comparison Period End"
                      },
                      "comparison_period_start": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional start date of comparison period. Defaults to same-length period immediately before analysis period.",
                        "title": "Comparison Period Start"
                      },
                      "metric": {
                        "default": "ctr",
                        "description": "Metric to analyze. Options: roas, ctr, cpc, cpm, conversions, conversion_rate, leads, engagement_rate, lead_form_completion_rate. Default: 'ctr'",
                        "title": "Metric",
                        "type": "string"
                      },
                      "period_end": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date of analysis period (YYYY-MM-DD format). Defaults to yesterday.",
                        "title": "Period End"
                      },
                      "period_start": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date of analysis period (YYYY-MM-DD format). Defaults to 7 days ago.",
                        "title": "Period Start"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for explain_linkedin_anomaly)"
                  },
                  "success": true,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: explain_linkedin_anomaly",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks why their LinkedIn metrics changed,\nwants to understand a performance drop/spike, or says something like\n\"M...",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "explain_linkedin_anomaly"
      }
    },
    "/api/v1/tools/explain_linkedin_objectives/execute": {
      "post": {
        "description": "User asks about LinkedIn campaign objectives or which one to choose.\n\nExplain all LinkedIn campaign objectives and recommend the best one.\n\nWhat this tool does:\n- Explains all 8 LinkedIn objectives in detail\n- Shows typical costs and expected results\n- Provides personalized recommendation based on user's goal\n- Helps user decide which objective to use\n\nParameters:\n- business_type: Type of business (optional)\n- goal: What user wants to achieve (optional)\n\nExample Prompts:\n- \"What objectives can I choose for LinkedIn?\"\n- \"Which objective should I use for my SaaS?\"\n- \"Explain LinkedIn campaign objectives\"\n- \"I want to get signups, which objective?\"\n\nExecution time: <1 second",
        "operationId": "execute_explain_linkedin_objectives",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "business_type": "string",
                  "goal": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for explaining LinkedIn campaign objectives",
                    "properties": {
                      "business_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Type of business (e.g., 'B2B SaaS', 'E-commerce', 'Agency')",
                        "title": "Business Type"
                      },
                      "goal": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "What the user wants to achieve (e.g., 'get signups', 'drive traffic', 'build awareness')",
                        "title": "Goal"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for explain_linkedin_objectives)"
                  },
                  "success": true,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: explain_linkedin_objectives",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_linkedin_objectives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about LinkedIn campaign objectives or which one to choose",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "explain_linkedin_objectives"
      }
    },
    "/api/v1/tools/explain_meta_anomaly/execute": {
      "post": {
        "description": "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.\n\nThis 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.\n\nReturns:\n- Current metric value vs historical averages (30/60/90-day)\n- Deviation percentages from baselines\n- Contributing factors ranked by estimated impact\n- Factor explanations with details\n- Similar historical periods with comparable changes\n- Assessment of anomaly severity (CRITICAL/WARNING/MINOR/NORMAL)\n- Specific recommendations based on detected factors\n- Quick actionable items\n\nWhen to use this tool:\n- \"Why did my Meta ROAS drop?\"\n- \"What happened to my Facebook CTR this week?\"\n- \"Why is my Instagram CPM so high?\"\n- \"Explain my Meta performance change\"\n- \"My conversions dropped last week - why?\"\n- \"Why did my frequency spike?\"\n- \"What caused my ROAS to decline 40%?\"\n- \"Diagnose my Meta performance problem\"\n\nParameters:\n- metric: Required - 'roas', 'ctr', 'cpc', 'cpm', 'conversions', 'conversion_rate', 'frequency', or 'reach'\n- period_start: Required - Start date (YYYY-MM-DD format)\n- period_end: Required - End date (YYYY-MM-DD format, must be \u226430 days from start)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 3-8 seconds (statistical analysis across multiple periods)\nData source: campaign_daily_metrics + meta_placement_daily_metrics + meta_ad_creative_metrics tables\n\nContributing Factors Detected:\n- CPM Change: Auction competition fluctuations (holidays, competitor activity)\n- CTR Change: Creative performance (fatigue, messaging, targeting)\n- Conversion Rate Change: Landing page or offer issues\n- Frequency Change: Audience saturation (Meta-specific)\n- Creative Fatigue: High frequency + declining CTR (Meta-specific)\n- Placement Mix Change: Shift in placement distribution (Meta-specific)\n- Campaign Changes: Paused/new campaigns affecting overall performance\n\nSeverity Levels:\n- \ud83d\udd34 CRITICAL (\u226540% deviation): Immediate action required\n- \ud83d\udfe1 WARNING (25-40% deviation): Review recommended\n- \ud83d\udfe2 MINOR (15-25% deviation): Monitor situation\n- \u2705 NORMAL (<15% deviation): Within normal variation\n\nSimilar Historical Periods:\nFinds past periods with comparable deviations to provide context (e.g., \"similar drop occurred during Black Friday 2024\")\n\nBest for:\n- Diagnosing sudden performance drops\n- Understanding seasonal patterns\n- Identifying actionable causes vs. external factors\n- Prioritizing optimization efforts",
        "operationId": "execute_explain_meta_anomaly",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "metric": "ctr",
                  "period_end": "string",
                  "period_start": "string",
                  "raw_data": false
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta anomaly explanation analysis",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "metric": {
                        "default": "ctr",
                        "description": "Metric to analyze: 'roas', 'ctr', 'cpc', 'cpm', 'conversions', 'conversion_rate', 'frequency', or 'reach'. Default: 'ctr'",
                        "title": "Metric",
                        "type": "string"
                      },
                      "period_end": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date of anomaly period (ISO format: YYYY-MM-DD). Defaults to yesterday. Period must be \u226430 days.",
                        "title": "Period End"
                      },
                      "period_start": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date of anomaly period (ISO format: YYYY-MM-DD). Defaults to 7 days ago.",
                        "title": "Period Start"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for explain_meta_anomaly)"
                  },
                  "success": true,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: explain_meta_anomaly",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_meta_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks why Meta/Facebook/Instagram performance dropped or changed, what happened to their ROAS/CTR/CPM, or wants t...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "explain_meta_anomaly"
      }
    },
    "/api/v1/tools/explain_performance_anomaly/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nExplain why a performance metric changed using statistical analysis and historical context.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times. Uses statistical analysis only (no ML models).\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1 Feature 4):**\n- Explains why metrics changed (ROAS, CTR, CPC, conversions, conversion rate)\n- Compares current period to historical averages (30/60/90-day)\n- Identifies contributing factors with severity levels\n- Detects campaign changes (paused, new, budget changes)\n- Finds similar historical periods (seasonality detection)\n- Provides actionable recommendations to address issues\n\n**Returns comprehensive anomaly explanation:**\n- Current metric value vs historical averages\n- Deviation percentages (how much it changed)\n- Contributing factors:\n  - CPC changes (>15% = auction competition shifts)\n  - Conversion rate changes (>10% = landing page/seasonality issues)\n  - Campaign changes (paused high-performers, new campaigns, budget changes)\n  - Day-of-week patterns (weekend vs weekday effects)\n- Similar historical periods for context\n- Assessment (normal variation vs requires action)\n- Specific recommendations to fix the issue\n\n\ud83d\udd0d **How Anomaly Detection Works:**\n\n**Historical Comparison:**\n- Compares current period to 30/60/90-day averages\n- \u00b115% deviation considered \"normal variation\"\n- >15% deviation flagged as requiring attention\n\n**Contributing Factor Detection:**\n1. **CPC Changes** (>15% threshold)\n   - Increased CPC = auction competition increased\n   - Decreased CPC = auction competition decreased or bid adjustments\n\n2. **Conversion Rate Changes** (>10% threshold)\n   - Decreased = landing page issues, seasonality, audience quality\n   - Increased = landing page improved, better targeting\n\n3. **Campaign Changes:**\n   - Paused high-performers (ROAS > 3.0x) = lost revenue driver\n   - New campaigns (>$1K spend) = learning phase affecting overall performance\n   - Budget changes (>20%) = delivery and auction participation affected\n\n4. **Day-of-Week Patterns:**\n   - Weekend-heavy periods often show different performance\n   - Normal for B2C (higher weekend conversion)\n   - Normal for B2B (lower weekend conversion)\n\n5. **Seasonality Detection:**\n   - Finds similar historical periods (\u00b110% metric value)\n   - Helps identify if drop is seasonal vs real issue\n\n**Parameters:**\n- metric: 'roas', 'ctr', 'cpc', 'conversions', 'conversion_rate' (REQUIRED)\n- period_start: Start date in YYYY-MM-DD format (REQUIRED)\n- period_end: End date in YYYY-MM-DD format (REQUIRED, max 30 days period)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 2-4 seconds (statistical analysis + database queries)\n**Data source:** campaign_daily_metrics table (updated nightly, 120-day retention)\n**Analysis method:** Statistical comparison (no ML models)\n**Trigger:** Reactive (user asks \"why?\"), not proactive alerts\n\n**Use this tool when:**\n- User asks \"why did my ROAS drop?\"\n- User asks \"why did my CTR increase?\"\n- User notices unexpected metric changes\n- User wants to understand performance fluctuations\n- After seeing performance changes in dashboards\n\n\ud83d\udcca **AFTER calling this tool, help the user understand:**\n\n**Normal vs Abnormal Variation:**\n- **Normal**: \u00b115% deviation, typical day-of-week/seasonal patterns\n- **Abnormal**: >15% deviation with specific contributing factors\n\n**Severity Levels:**\n- \ud83d\udd34 **HIGH**: >25% change with clear cause (CPC spike, campaign paused)\n- \ud83d\udfe1 **MEDIUM**: 15-25% change or multiple minor factors\n- \ud83d\udfe2 **LOW**: <15% change, likely normal variation\n\n**Example Interpretation:**\n\"Your ROAS dropped 33% from 4.2x to 2.8x. This was caused by two high-severity factors:\n1. CPC increased 25% (auction competition spike during holiday season)\n2. Your top campaign 'Brand - Exact' was paused, losing $240/day in high-ROAS revenue\n\nThis is NOT normal variation - these are actionable issues. Recommendations:\n- Reduce bids by 10-15% to counter CPC inflation\n- Re-enable 'Brand - Exact' campaign if budget allows\n- Review landing page conversion rate (also dropped 12%)\"\n\n**Similar Period Context:**\nIf tool finds similar historical periods, explain seasonality:\n\"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.\"\n\n**Quick Actions:**\nBased on contributing factors, prioritize:\n1. **Campaign paused** \u2192 Re-enable high performers immediately\n2. **CPC spike** \u2192 Adjust bids, improve Quality Score\n3. **Conversion rate drop** \u2192 Review landing page, check for bugs\n4. **Budget changes** \u2192 Monitor delivery as it stabilizes\n5. **Weekend effect** \u2192 Normal variation, no action needed\n\n**Visualization Tip:**\nSuggest 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\").\n\n**Important Notes:**\n- This is REACTIVE explanation, not proactive monitoring\n- Max 120-day historical lookback (database retention limit)\n- Uses simple statistics (mean, variance), no ML predictions\n- Focus on actionable factors user can control\n- Consider seasonality when interpreting results\n\n**Best Practices:**\n- Run this tool when you notice >15% metric changes\n- Compare multiple time periods to confirm trends\n- Cross-reference with other tools (wasted spend, budget optimizer)\n- Use for post-mortem analysis of performance changes\n- Help user distinguish normal variation from real issues\n\n\ud83d\udcac **Community**: For anomaly analysis discussions, visit our Discord: https://discord.gg/dH3Qt4YS",
        "operationId": "execute_explain_performance_anomaly",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "customer_id": "string",
                  "metric": "string",
                  "period_end": "string",
                  "period_start": "string",
                  "raw_data": false
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for performance anomaly explanation",
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "metric": {
                        "description": "Metric to explain: 'roas' (Return on Ad Spend), 'ctr' (Click-Through Rate), 'cpc' (Cost Per Click), 'conversions', or 'conversion_rate'",
                        "title": "Metric",
                        "type": "string"
                      },
                      "period_end": {
                        "description": "End date of the anomaly period (ISO format: YYYY-MM-DD, e.g., '2025-01-21'). Maximum 30 days between start and end.",
                        "title": "Period End",
                        "type": "string"
                      },
                      "period_start": {
                        "description": "Start date of the anomaly period (ISO format: YYYY-MM-DD, e.g., '2025-01-15')",
                        "title": "Period Start",
                        "type": "string"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "metric",
                      "period_start",
                      "period_end"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for explain_performance_anomaly)"
                  },
                  "success": true,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: explain_performance_anomaly",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_performance_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "explain_performance_anomaly"
      }
    },
    "/api/v1/tools/explain_tiktok_anomaly/execute": {
      "post": {
        "description": "Explain why a TikTok metric changed during a specific period. Uses statistical analysis and factor detection.\n\nSupported metrics: roas, ctr, cpc, cpm, conversions, conversion_rate, hook_rate, video_completion_rate, engagement_rate\n\nReturns: current value vs historical averages, deviation %, contributing factors (CPM change, CTR change, hook rate change, engagement change, campaign structure changes), assessment, recommendations.\n\nWhen to use: \"Why did my TikTok CTR drop?\", \"Explain TikTok performance change\", \"What happened to my TikTok ROAS?\"\n\nParameters:\n- metric: REQUIRED \u2014 which metric to analyze\n- period_start/period_end: REQUIRED \u2014 YYYY-MM-DD dates for the anomaly period (max 30 days)",
        "operationId": "execute_explain_tiktok_anomaly",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "metric": "ctr",
                  "period_end": "string",
                  "period_start": "string",
                  "raw_data": false
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "metric": {
                        "default": "ctr",
                        "description": "Metric: roas, ctr, cpc, cpm, conversions, hook_rate, engagement_rate",
                        "title": "Metric",
                        "type": "string"
                      },
                      "period_end": {
                        "description": "Anomaly period end YYYY-MM-DD",
                        "title": "Period End",
                        "type": "string"
                      },
                      "period_start": {
                        "description": "Anomaly period start YYYY-MM-DD",
                        "title": "Period Start",
                        "type": "string"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "period_start",
                      "period_end"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for explain_tiktok_anomaly)"
                  },
                  "success": true,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: explain_tiktok_anomaly",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "explain_tiktok_anomaly"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Explain why a TikTok metric changed during a specific period",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "explain_tiktok_anomaly"
      }
    },
    "/api/v1/tools/generate_linkedin_ad_creatives/execute": {
      "post": {
        "description": "User needs ad copy for LinkedIn campaigns.\n\nGenerate multiple LinkedIn ad creative variations with different angles.\n\nWhat this tool does:\n- Creates 4 ad variations (problem-focused, solution-focused, social proof, curiosity)\n- Generates introductory text (600 char max)\n- Creates headlines (70 char max)\n- Suggests appropriate CTAs\n- Adds UTM tracking to landing pages\n\nParameters:\n- business_name: Name of the business (required)\n- business_description: What the business does (required)\n- target_audience: Who to target (required)\n- value_proposition: Main benefit (required)\n- landing_page_url: Where to send traffic (required)\n- campaign_objective: Campaign goal (optional, default: WEBSITE_VISIT)\n- tone: Voice style (optional: professional, casual, urgent, inspirational)\n- include_stats: Include statistics (optional)\n- stats_to_include: Stats to use (optional)\n\nReturns:\n- 4 complete ad creative variations\n- Each with: intro text, headline, CTA, landing URL with UTM\n\nExample Prompts:\n- \"Generate ad copy for my LinkedIn campaign\"\n- \"Create ad variations for my B2B SaaS\"\n- \"Help me write LinkedIn ads for my marketing tool\"\n\nExecution time: <1 second",
        "operationId": "execute_generate_linkedin_ad_creatives",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "business_description": "string",
                  "business_name": "string",
                  "campaign_objective": "WEBSITE_VISIT",
                  "include_stats": false,
                  "landing_page_url": "https://example.com",
                  "stats_to_include": "string",
                  "target_audience": "string",
                  "tone": "professional",
                  "value_proposition": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for generating LinkedIn ad creative variations",
                    "properties": {
                      "business_description": {
                        "description": "Brief description of what the business does (1-2 sentences)",
                        "title": "Business Description",
                        "type": "string"
                      },
                      "business_name": {
                        "description": "Name of the business/product being advertised",
                        "title": "Business Name",
                        "type": "string"
                      },
                      "campaign_objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "WEBSITE_VISIT",
                        "description": "Campaign objective: WEBSITE_VISIT, LEAD_GENERATION, BRAND_AWARENESS, etc.",
                        "title": "Campaign Objective"
                      },
                      "include_stats": {
                        "anyOf": [
                          {
                            "type": "boolean"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": false,
                        "description": "Whether to include statistics/numbers in copy (user should provide if True)",
                        "title": "Include Stats"
                      },
                      "landing_page_url": {
                        "description": "URL where users will be directed when they click the ad",
                        "title": "Landing Page Url",
                        "type": "string"
                      },
                      "stats_to_include": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Statistics to potentially include (e.g., '500+ customers', '10 hours saved/week')",
                        "title": "Stats To Include"
                      },
                      "target_audience": {
                        "description": "Description of who the ad is targeting (e.g., 'Marketing managers at SaaS companies')",
                        "title": "Target Audience",
                        "type": "string"
                      },
                      "tone": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "professional",
                        "description": "Tone of voice: 'professional', 'casual', 'urgent', 'inspirational'",
                        "title": "Tone"
                      },
                      "value_proposition": {
                        "description": "Main benefit or value the product/service provides",
                        "title": "Value Proposition",
                        "type": "string"
                      }
                    },
                    "required": [
                      "business_name",
                      "business_description",
                      "target_audience",
                      "value_proposition",
                      "landing_page_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for generate_linkedin_ad_creatives)"
                  },
                  "success": true,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: generate_linkedin_ad_creatives",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_linkedin_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User needs ad copy for LinkedIn campaigns",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "generate_linkedin_ad_creatives"
      }
    },
    "/api/v1/tools/generate_report_now/execute": {
      "post": {
        "description": "Generate an immediate performance report and deliver it now.\n\n**What it does:**\n- Generates a comprehensive PDF performance report immediately\n- Fetches latest campaign data from all connected platforms\n- Includes AI-powered recommendations and insights\n- Delivers via email, Slack, or webhook\n\n**When to use:**\n- \"Get me my campaign performance report\"\n- \"Send me a detailed analysis of all my accounts\"\n- \"Generate a report and email it to me\"\n- \"I need my performance data now\"\n- \"Prepare a report for all my campaigns\"\n\n**Report Types:**\n- performance_brief: Quick overview of key metrics\n- detailed_analysis: Deep dive with breakdowns\n- executive_summary: High-level summary for stakeholders\n\n**Execution time:** 2-5 minutes (async - you'll be notified when ready)\n\n**Example:**\nUser: \"Get me my detailed campaign performance for all accounts and email it to me\"\n\u2192 Call generate_report_now with:\n  - report_type: \"detailed_analysis\"\n  - platforms: null (all platforms)\n  - delivery_method: \"email\"\n  - delivery_destination: \"user@example.com\"",
        "operationId": "execute_generate_report_now",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_ids": [
                    "string"
                  ],
                  "campaign_ids": [
                    "string"
                  ],
                  "date_range": {
                    "end_date": "2026-03-31",
                    "start_date": "2026-03-01"
                  },
                  "delivery_method": "email",
                  "platforms": [
                    "string"
                  ],
                  "report_type": "performance_brief"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for generating an immediate ad-hoc report.",
                    "properties": {
                      "account_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Specific account IDs to include. Default is all accounts.",
                        "title": "Account Ids"
                      },
                      "campaign_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Specific campaign IDs to include. Default is all campaigns.",
                        "title": "Campaign Ids"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "additionalProperties": {
                              "type": "string"
                            },
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Date range with 'start_date' and 'end_date' in YYYY-MM-DD format. Default is last 7 days.",
                        "title": "Date Range"
                      },
                      "delivery_destination": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Email address, Slack channel, or webhook URL. Default is user's email.",
                        "title": "Delivery Destination"
                      },
                      "delivery_method": {
                        "default": "email",
                        "description": "How to deliver the report: 'email', 'slack', or 'webhook'",
                        "title": "Delivery Method",
                        "type": "string"
                      },
                      "include_recommendations": {
                        "default": true,
                        "description": "Whether to include AI-powered recommendations in the report",
                        "title": "Include Recommendations",
                        "type": "boolean"
                      },
                      "platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Platforms to include: 'google_ads', 'meta_ads', 'tiktok_ads', 'linkedin_ads'. Default is all connected.",
                        "title": "Platforms"
                      },
                      "report_format": {
                        "default": "pdf",
                        "description": "Output format: 'pdf', 'html', or 'json'",
                        "title": "Report Format",
                        "type": "string"
                      },
                      "report_type": {
                        "default": "performance_brief",
                        "description": "Type of report: 'performance_brief', 'detailed_analysis', or 'executive_summary'",
                        "title": "Report Type",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for generate_report_now)"
                  },
                  "success": true,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: generate_report_now",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "generate_report_now"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Generate an immediate performance report and deliver it now [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "generate_report_now"
      }
    },
    "/api/v1/tools/get_benchmark_context/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGet industry benchmark context for AI-powered recommendations.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1 Feature 5):**\n- Generates contextual benchmark data for the user's business\n- Combines business profile with industry benchmarks\n- Provides recommended ROAS targets based on vertical\n- Returns formatted context for AI prompts\n\n**Returns benchmark context:**\n- Business vertical and size context\n- Industry-specific ROAS benchmarks (typical, good, excellent)\n- CTR benchmarks by industry\n- CPC expectations for the vertical\n- Seasonality considerations\n- Custom recommendations based on profile\n\n**Industry Benchmarks Included:**\n| Vertical | Typical ROAS | Good ROAS | Excellent ROAS |\n|----------|--------------|-----------|----------------|\n| Retail | 4.0x | 6.0x | 8.0x |\n| Services | 3.0x | 5.0x | 7.0x |\n| Technology | 3.5x | 5.5x | 8.0x |\n| Healthcare | 2.5x | 4.0x | 6.0x |\n| Finance | 5.0x | 8.0x | 12.0x |\n| Education | 2.0x | 3.5x | 5.0x |\n\n**Parameters:**\n- **include_recommendations**: Include performance recommendations (default: true)\n- **customer_id**: Optional (uses connected account if omitted)\n\n**Use this tool when:**\n- Before providing performance analysis\n- When comparing user's metrics to industry standards\n- User asks \"how am I doing compared to others?\"\n- You need context for optimization recommendations\n\n**Integration with Other Tools:**\nCall this BEFORE or AFTER these tools for enhanced recommendations:\n- `analyze_wasted_spend` - Contextualize waste against industry norms\n- `optimize_budget_allocation` - Use industry-appropriate ROAS targets\n- `get_campaign_performance` - Compare metrics to benchmarks\n- `explain_performance_anomaly` - Understand if changes are industry-wide\n\n**Execution time:** 1-2 seconds (profile lookup + benchmark calculation)",
        "operationId": "execute_get_benchmark_context",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "customer_id": "string",
                  "include_recommendations": true
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting benchmark context for AI prompts",
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "include_recommendations": {
                        "default": true,
                        "description": "Include performance recommendations based on benchmarks (default: true)",
                        "title": "Include Recommendations",
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_benchmark_context)"
                  },
                  "success": true,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_benchmark_context",
                  "is_error": true,
                  "success": false,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_benchmark_context"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_benchmark_context"
      }
    },
    "/api/v1/tools/get_business_profile/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGet the user's business profile for contextual recommendations.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1 Feature 5):**\n- Retrieves the user's saved business profile\n- Returns business vertical, size, goals, target audience\n- Provides context for more relevant recommendations\n- Shows profile source (user-set, inferred, or none)\n\n**Returns business context:**\n- Business vertical (retail, services, technology, etc.)\n- Business size (small, medium, large)\n- Primary goal (leads, sales, awareness, traffic)\n- Target audience description\n- Geographic focus (local, regional, national, international)\n- Seasonality patterns\n- Profile confidence level\n\n**Use this tool when:**\n- You need business context for recommendations\n- Before providing industry-specific advice\n- User asks \"what type of business am I?\"\n- You want to personalize optimization suggestions\n\n**If no profile exists:**\n- Use `infer_business_profile` to automatically detect from campaign data\n- Or ask user to provide business details via `save_business_profile`\n\n**Execution time:** 1-2 seconds (database lookup)",
        "operationId": "execute_get_business_profile",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting business profile",
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_business_profile)"
                  },
                  "success": true,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_business_profile",
                  "is_error": true,
                  "success": false,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_business_profile"
      }
    },
    "/api/v1/tools/get_campaign_performance/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAnalyze Google Ads campaign performance with comprehensive insights and recommendations.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.\n\n**Returns detailed analysis:**\n- Campaign structure and ad group organization\n- Keyword performance with quality scores and match types\n- Ad group performance breakdown\n- Performance metrics (CTR, conversions, CPC, cost, ROAS)\n- Optimization recommendations with actionable insights\n- Performance trends (last 7 days)\n\n**Parameters:**\n- lookback_days: 7, 30, 60, 90, or 120 days (default: 30)\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 2-5 seconds (direct backend API call)\n**Data source:** Cached database (updated nightly via metrics collection)\n\n**Use this tool to:**\n- Review current campaign performance\n- Identify optimization opportunities\n- Get data-driven recommendations\n- Analyze keyword and ad effectiveness\n- Understand what's working and what needs improvement\n\n\ud83d\udcca **AFTER calling this tool, provide these insights to the user:**\n\n**How to Interpret Metrics:**\n- **CTR (Click-Through Rate):** Industry average is 3-5% for search ads\n  - Above 5%: Excellent ad relevance\n  - Below 2%: Consider improving ad copy or targeting\n\n- **Conversion Rate:** Industry average is 2-5%\n  - Low CR + High CTR = Landing page issue\n  - Low CR + Low CTR = Ad/targeting issue\n\n- **CPA (Cost Per Acquisition):** Compare to your target CPA\n  - Track the trend over time\n  - Adjust bids if CPA is consistently too high\n\n\ud83d\udca1 **Optimization Tips:**\n- Wait at least 2 weeks before making major changes (learning phase)\n- Focus on high-performing keywords and pause low performers\n- Test different ad copy variations (A/B testing)\n- Adjust bids based on device/location performance\n- Review search terms report for negative keyword opportunities\n\n\ud83d\udcac **Community**: For optimization discussions and tips, visit our Discord: https://discord.gg/dH3Qt4YS",
        "operationId": "execute_get_campaign_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "customer_id": "string",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_campaign_performance)"
                  },
                  "success": true,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_campaign_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_campaign_performance"
      }
    },
    "/api/v1/tools/get_campaign_structure/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGet campaign structure with ad groups, keywords, ads, and extensions. Supports pagination for large campaigns.\n\nThis tool retrieves READ-ONLY data. Safe to call multiple times.\n\n**Returns campaign hierarchy (paginated by ad groups):**\n- Campaign details (name, status, budget, bidding strategy)\n- Ad groups with their settings (paginated \u2014 default 5 per page)\n- Keywords per ad group (including match types, bids, status)\n- Ads per ad group (including RSA headlines/descriptions, ad strength)\n- Negative keywords per ad group\n- Campaign-level extensions and negative keywords (on page 1 only)\n- Pagination metadata (page, total_pages, has_more)\n- Summary counts (total ad groups, keywords, ads across ALL pages)\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)\n- customer_id: Optional (uses connected account if omitted)\n- page: Page number, 1-based (default: 1)\n- page_size: Ad groups per page, 1-50 (default: 5)\n- ad_group_id: Optional \u2014 fetch only this single ad group (bypasses pagination)\n- include: What detail per ad group: 'all' (default), 'summary' (counts only), 'keywords', 'ads'\n\n**\u26a1 PAGINATION CONTRACT (IMPORTANT FOR AI AGENTS):**\n- The response includes `pagination.has_more` \u2014 if true, you MUST call again with `page` incremented\n- Continue calling until `has_more` is false\n- Then consolidate all pages and present the complete campaign structure to the user\n- The `summary` section is always included and shows total counts across ALL ad groups (not just the current page)\n- Extensions and campaign-level negative keywords are only returned on page 1\n- Use `ad_group_id` to drill into a single ad group when the user asks about a specific one\n- Use `include='summary'` for a quick overview without nested keyword/ad data\n\n**Execution time:** 3-8 seconds per page (multiple API queries)\n\n**Use this tool when:**\n- User wants to update an existing campaign\n- User wants to see current keywords/ads\n- User wants to add extensions to existing campaign\n- User says \"show me what's in this campaign\"\n- Before making any updates to a campaign\n\n**Important IDs returned:**\n- campaign.id - For campaign-level updates (budget, status)\n- ad_groups[].id - For adding keywords/ads\n- keywords[].id - For keyword updates (status, bids)\n- ads[].id - For ad content updates\n\n**Example flow:**\n1. User: \"I want to update my campaign\"\n2. Agent: Uses list_campaigns to show all campaigns\n3. User: Selects campaign \"Summer Sale 2025\"\n4. Agent: Uses get_campaign_structure with that campaign_id\n5. If has_more=true, agent calls again with page=2, page=3, etc.\n6. Agent: Shows consolidated structure and asks what to update\n7. User: \"Change the headlines\"\n8. Agent: Uses update_ad_headlines with the ad_id from structure",
        "operationId": "execute_get_campaign_structure",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "include": "all",
                  "page": 1,
                  "page_size": 5
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting campaign structure with pagination",
                    "properties": {
                      "ad_group_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Fetch only this specific ad group's details (bypasses pagination)",
                        "title": "Ad Group Id"
                      },
                      "campaign_id": {
                        "description": "The campaign ID to get structure for. Use list_campaigns first to get available campaign IDs.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "include": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "all",
                        "description": "What to include per ad group: 'all' (default), 'summary' (counts only, no nested data), 'keywords' (keywords only), 'ads' (ads only)",
                        "title": "Include"
                      },
                      "page": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 1,
                        "description": "Page number (1-based). Use to paginate through large campaigns. Default: 1",
                        "title": "Page"
                      },
                      "page_size": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 5,
                        "description": "Number of ad groups per page (1-50). Default: 5",
                        "title": "Page Size"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_campaign_structure)"
                  },
                  "success": true,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_campaign_structure",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_campaign_structure"
      }
    },
    "/api/v1/tools/get_campaign_targeting/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGet location/geo targeting for a Google Ads campaign.\n\nThis tool retrieves READ-ONLY data. Safe to call multiple times.\n\n**Returns location targeting details:**\n- Targeted locations (cities, states, countries, metros the campaign targets)\n- Excluded locations (negative geo targets)\n- Bid modifiers per location (if set)\n- Location type (Country, State, City, Metro, etc.)\n- Summary counts (targeted, excluded, total)\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 1-3 seconds (single API query)\n\n**Use this tool when:**\n- User asks \"where is this campaign targeting?\" or \"what locations?\"\n- User wants to see geo targeting for a campaign\n- User asks \"what countries/cities/states does my campaign target?\"\n- Before recommending location targeting changes\n- To verify location setup after campaign creation\n\n**Returns for each location:**\n- name: Short location name (e.g., \"New York\")\n- canonical_name: Full hierarchical name (e.g., \"New York,New York,United States\")\n- country_code: ISO country code (e.g., \"US\")\n- target_type: Location type (Country, State, City, County, Metro, etc.)\n- is_negative: true if this location is EXCLUDED\n- bid_modifier: Bid adjustment for this location (null if no modifier)\n\n**Example flow:**\n1. User: \"What locations does my Diamond Ring campaign target?\"\n2. Agent: Uses get_campaign_targeting with campaign_id\n3. Returns: Targeted: United States, New York, Los Angeles; Excluded: none",
        "operationId": "execute_get_campaign_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting campaign location targeting",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to get location targeting for. Use list_campaigns first.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_campaign_targeting)"
                  },
                  "success": true,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_campaign_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_campaign_targeting"
      }
    },
    "/api/v1/tools/get_connections_status/execute": {
      "post": {
        "description": "View connected ad accounts and OAuth connections.\n\nShows all connected ad platforms (Google Ads, TikTok Ads, Meta Ads, LinkedIn Ads) with:\n- Connection status and account details\n- Primary account indicator\n- Option to switch primary account\n- Quick links to connect new platforms\n\nUse this tool when user asks about:\n- \"how many accounts do I have\" / \"how many ad accounts\"\n- \"what accounts are connected\" / \"connected accounts\"\n- \"which account am I using\" / \"current account\"\n- \"show my connections\" / \"list my accounts\"\n- \"connection status\" / \"account status\"\n- \"what platforms do I have connected\"\n- Account count, account list, or connection overview\n\nDo not use for:\n- Discovering campaigns, ads, or keywords (use discover_existing_assets)\n- Performance data or metrics (use performance tools)\n- Creating or managing campaigns\n\nThis is specifically for OAuth connections and account management.",
        "operationId": "execute_get_connections_status",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {}
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for get_connections_status tool - no required params",
                    "properties": {},
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_connections_status)"
                  },
                  "success": true,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_connections_status",
                  "is_error": true,
                  "success": false,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_connections_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "View connected ad accounts and OAuth connections [write]",
        "tags": [
          "general"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "get_connections_status"
      }
    },
    "/api/v1/tools/get_linkedin_audience_insights/execute": {
      "post": {
        "description": "User asks about B2B audience demographics,\nwhich professional segments perform best, targeting optimization, or wants to understand\ntheir LinkedIn audience breakdown.\n\nTHIS IS LINKEDIN'S KILLER FEATURE - Professional demographic analysis:\n\nReturns B2B audience performance breakdown by:\n- Seniority (Entry, Senior, Manager, Director, VP, C-Suite)\n- Industry (Technology, Finance, Healthcare, etc.)\n- Company Size (1-10, 11-50, 51-200, 201-500, 501-1000, 1000+)\n- Job Function (Marketing, Sales, IT, Engineering, HR, etc.)\n\nEach segment includes:\n- Impressions, clicks, spend, conversions\n- CTR, CPL, ROAS, engagement rate\n- Category: SCALE, MAINTAIN, REDUCE, or EXCLUDE\n\nIdentifies:\n- Best performing segments (to scale)\n- Underperforming segments (to reduce/exclude)\n- Wasted spend on B2B segments\n- Targeting optimization recommendations\n\nParameters:\n- lookback_days: Number of days to analyze (7-120). Default: 30\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- breakdown_types: Optional list of specific breakdowns. Default: all types\n- target_roas: Override target ROAS for categorization\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"Which seniority levels convert best on LinkedIn?\"\n- \"Show me LinkedIn audience breakdown by industry\"\n- \"What company sizes are we reaching on LinkedIn?\"\n- \"Am I reaching decision-makers on LinkedIn?\"\n- \"Which B2B segments should I exclude?\"\n\nExecution time: 4-6 seconds",
        "operationId": "execute_get_linkedin_audience_insights",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "breakdown_types": [
                    "string"
                  ],
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting LinkedIn B2B audience insights",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "breakdown_types": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "List of breakdown types to include: 'seniority', 'industry', 'company_size', 'job_function'. Default: all types.",
                        "title": "Breakdown Types"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7-120). Default: 30.",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "minimum": 0.1,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Override target ROAS for categorization. If not provided, uses historical average or 2.0x default.",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_linkedin_audience_insights)"
                  },
                  "success": true,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_linkedin_audience_insights",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about B2B audience demographics,\nwhich professional segments perform best, targeting optimization, or wants...",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_linkedin_audience_insights"
      }
    },
    "/api/v1/tools/get_linkedin_campaign_performance/execute": {
      "post": {
        "description": "User asks about LinkedIn Ads performance, campaign metrics,\nB2B engagement, ROAS, or wants to understand how their LinkedIn campaigns are performing.\n\nReturns comprehensive LinkedIn Ads performance metrics including:\n- Account summary (total spend, impressions, clicks, CTR, conversions, ROAS)\n- LinkedIn-specific metrics (engagements, leads, cost per lead)\n- Campaign breakdown with performance categorization\n- Trend data (daily metrics, week-over-week, month-over-month changes)\n- Actionable recommendations\n\nParameters:\n- lookback_days: Number of days to analyze (7, 14, 30, 60, 90). Default: 30\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- include_campaigns: Include per-campaign breakdown. Default: true\n- include_trends: Include trend analysis. Default: true\n- include_comprehensive: Include creative/engagement analysis. Default: true\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"How are my LinkedIn ads performing?\"\n- \"Show me LinkedIn campaign performance for the last 30 days\"\n- \"What's my LinkedIn ROAS this month?\"\n- \"Which LinkedIn campaigns are performing best?\"\n\nExecution time: 3-5 seconds",
        "operationId": "execute_get_linkedin_campaign_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "include_campaigns": true,
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting LinkedIn campaign performance metrics",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ads account ID (optional - uses default connected account)",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter performance to a specific campaign ID. If not provided, returns account-wide performance.",
                        "title": "Campaign Id"
                      },
                      "campaign_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter performance by campaign name (partial match). If not provided, returns all campaigns.",
                        "title": "Campaign Name"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "include_campaigns": {
                        "default": true,
                        "description": "Include per-campaign breakdown. Default: true",
                        "title": "Include Campaigns",
                        "type": "boolean"
                      },
                      "include_comprehensive": {
                        "default": true,
                        "description": "Include creative and engagement analysis. Default: true",
                        "title": "Include Comprehensive",
                        "type": "boolean"
                      },
                      "include_trends": {
                        "default": true,
                        "description": "Include trend data (daily metrics, WoW/MoM changes). Default: true",
                        "title": "Include Trends",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "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.",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_linkedin_campaign_performance)"
                  },
                  "success": true,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_linkedin_campaign_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about LinkedIn Ads performance, campaign metrics,\nB2B engagement, ROAS, or wants to understand how their Li...",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_linkedin_campaign_performance"
      }
    },
    "/api/v1/tools/get_linkedin_campaign_structure/execute": {
      "post": {
        "description": "User wants full details about a specific LinkedIn campaign.\n\nGet complete campaign structure including creatives, targeting, and configuration.\n\nReturns:\n- Campaign details (name, status, objective, budget, schedule)\n- All creatives/ads linked to campaign\n- Full targeting criteria with resolved names\n- Conversion tracking setup\n- Campaign Manager link\n\nParameters:\n- campaign_id: Campaign ID to fetch (required)\n- account_id: Optional LinkedIn Ad Account ID\n\nExample Prompts:\n- \"Show me details for campaign 123456\"\n- \"What's the targeting for my LinkedIn campaign?\"\n- \"How many ads are in my campaign?\"\n\nExecution time: 3-5 seconds",
        "operationId": "execute_get_linkedin_campaign_structure",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting full campaign structure",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional - uses default connected account)",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to fetch structure for",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_linkedin_campaign_structure)"
                  },
                  "success": true,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_linkedin_campaign_structure",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_structure"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants full details about a specific LinkedIn campaign",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_linkedin_campaign_structure"
      }
    },
    "/api/v1/tools/get_linkedin_campaign_targeting/execute": {
      "post": {
        "description": "User wants to copy targeting from one campaign to another.\n\nGet targeting criteria from a campaign in a format ready for reuse.\n\nReturns:\n- All targeting URNs with human-readable names\n- Format ready to pass to create_linkedin_image_campaign\n\nUse Case:\n1. Get targeting from successful campaign\n2. Use those URNs in a new campaign\n\nExecution time: 2-3 seconds",
        "operationId": "execute_get_linkedin_campaign_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting campaign targeting",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to get targeting from",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_linkedin_campaign_targeting)"
                  },
                  "success": true,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_linkedin_campaign_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to copy targeting from one campaign to another",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_linkedin_campaign_targeting"
      }
    },
    "/api/v1/tools/get_linkedin_engagement_metrics/execute": {
      "post": {
        "description": "User asks specifically about LinkedIn engagement,\nsocial actions, lead generation metrics, or video performance.\n\nReturns detailed LinkedIn engagement breakdown:\n- Social engagement (likes, comments, shares, follows, reactions)\n- Lead generation (one-click leads, form opens, completion rates)\n- Video metrics (views, starts, completions, watch rates)\n- Viral metrics (organic share impressions, clicks)\n- Engagement rate calculations and benchmarks\n\nParameters:\n- lookback_days: Number of days to analyze (7-120). Default: 30\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"Show me LinkedIn engagement metrics\"\n- \"How many leads did we generate on LinkedIn?\"\n- \"What's our LinkedIn video completion rate?\"\n- \"Are people sharing our LinkedIn ads?\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_get_linkedin_engagement_metrics",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting LinkedIn engagement metrics breakdown",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter to a specific campaign ID.",
                        "title": "Campaign Id"
                      },
                      "campaign_name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by campaign name (partial match).",
                        "title": "Campaign Name"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7-120). Default: 30.",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_linkedin_engagement_metrics)"
                  },
                  "success": true,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_linkedin_engagement_metrics",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_engagement_metrics"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks specifically about LinkedIn engagement,\nsocial actions, lead generation metrics, or video performance",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_linkedin_engagement_metrics"
      }
    },
    "/api/v1/tools/get_linkedin_organizations/execute": {
      "post": {
        "description": "Fetch the LinkedIn Organizations (Company Pages) AND Ad Accounts the user can manage.\n\nIMPORTANT: Call this tool during campaign creation discovery phase to get both organization_id AND account_id.\n\nWhat this tool does:\n- Queries LinkedIn for all Company Pages the user has admin access to\n- Queries LinkedIn for all Ad Accounts the user can access\n- Returns organization IDs, names, AND ad account IDs\n- No parameters required - uses connected LinkedIn account\n\nReturns:\n- List of organizations with ID, name, and LinkedIn URL\n- List of ad accounts with ID, name, status, and linked organization\n- Both IDs needed for different operations\n\nUse this tool to:\n- Get the organization_id automatically (no need to ask user!)\n- Get the account_id for asset discovery (required for finding images/videos!)\n- Verify user has access to a Company Page\n- Find which page to use for Sponsored Content\n\nAfter getting organizations & accounts:\n- Use the `account_id` in `discover_linkedin_assets` to find existing images/videos\n- Use the `organization_id` in whichever campaign creation tool the user selected:\n  - `create_linkedin_image_campaign` (Single Image)\n  - `create_linkedin_video_campaign` (Video)\n  - `create_linkedin_carousel_campaign` (Carousel)\n  - `create_linkedin_text_campaign` (Text Ad)\n- Assets are tied to ad accounts, not organizations - that's why account_id is needed\n\nExecution time: 2-3 seconds",
        "operationId": "execute_get_linkedin_organizations",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting LinkedIn Organizations (Company Pages)",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (required for multi-account users, used for authentication). Get from list_connected_accounts.",
                        "title": "Account Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_linkedin_organizations)"
                  },
                  "success": true,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_linkedin_organizations",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_linkedin_organizations"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Fetch the LinkedIn Organizations (Company Pages) AND Ad Accounts the user can manage",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_linkedin_organizations"
      }
    },
    "/api/v1/tools/get_meta_ad_creatives/execute": {
      "post": {
        "description": "User wants to see their Meta ad creatives, ad copy, media URLs, or creative performance.\n\nReturns ad-level data including full creative content and media URLs alongside performance metrics.\n\nWhen to use this tool:\n- \"Show me my Meta ad creatives\"\n- \"What ads are running and how are they performing?\"\n- \"Show me the headlines and images for my ads\"\n- \"Which ad creatives are performing best?\"\n- \"What's the creative content for campaign X?\"\n- \"Show me ad performance with creative details\"\n- \"Get me the image URLs for my ads\"\n- \"Export my ad copy\"\n- \"What images and videos are my ads using?\"\n\nReturns per ad \u2014 ALWAYS display ALL of these fields when present:\n- Ad copy: headline, primary_text, description, call_to_action_type\n- Media URLs: image_url (direct CDN link to ad image), thumbnail_url, video_url (playable video source), video_id\n- Landing page: landing_page_url\n- Carousel cards: carousel_cards array with per-card image_url, headline, description, landing_page_url\n- Performance: spend, impressions, clicks, CTR, CPC, reach, frequency\n- Video engagement: 25%, 50%, 75%, 100% watched (for video ads)\n- Creative metadata: creative_type (image/video/carousel/dynamic_creative), first_seen_date\n\nIMPORTANT: Always show the full image_url, thumbnail_url, video_url, and landing_page_url values \u2014 these are direct CDN links users need to download/export their creative media. Do not summarize or omit URLs.\n\nParameters:\n- lookback_days: Number of days to analyze (7, 14, 30, 60, 90). Default: 30\n- campaign_id: Optional filter to specific campaign\n- ad_set_id: Optional filter to specific ad set\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n- start_date/end_date: Optional custom date range (overrides lookback_days)\n- limit: Max ads per page (default 20, max 50). Use for pagination.\n- offset: Number of ads to skip (default 0). Use with limit to page through results.\n\nPagination: Results are paginated. Response includes total_ads count and has_more flag. To get next page, increase offset by limit.\n\nExecution time: 1-3 seconds\nData source: Cached database (collected daily)",
        "operationId": "execute_get_meta_ad_creatives",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_set_id": "string",
                  "campaign_id": "string",
                  "date_range": "string",
                  "end_date": "string",
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta ad creatives with content and performance",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "ad_set_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter to specific ad set ID (optional)",
                        "title": "Ad Set Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter to specific campaign ID (optional)",
                        "title": "Campaign Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "limit": {
                        "default": 20,
                        "description": "Maximum number of ads to return per page (default 20, max 50). Use with offset for pagination.",
                        "title": "Limit",
                        "type": "integer"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "offset": {
                        "default": 0,
                        "description": "Number of ads to skip for pagination (default 0). Use with limit to page through results.",
                        "title": "Offset",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_meta_ad_creatives)"
                  },
                  "success": true,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_meta_ad_creatives",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_ad_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see their Meta ad creatives, ad copy, media URLs, or creative performance",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_meta_ad_creatives"
      }
    },
    "/api/v1/tools/get_meta_audience_insights/execute": {
      "post": {
        "description": "User asks about audience demographics, which placements perform best, device breakdown, or targeting optimization for Meta ads.\n\nThis tool provides audience and placement analysis for Meta campaigns.\n\nReturns:\n- Age group performance (18-24, 25-34, 35-44, 45-54, 55-64, 65+)\n- Gender performance breakdown\n- Placement breakdown (Facebook Feed, Instagram Feed, Stories, Reels, Messenger)\n- Device breakdown (Mobile, Desktop, Tablet)\n- Best performing audience segments\n- Targeting recommendations\n\nWhen to use this tool:\n- \"What age group performs best for my Meta ads?\"\n- \"Should I target men or women?\"\n- \"Which placements should I use?\"\n- \"Do my Instagram Stories ads perform well?\"\n- \"Mobile vs desktop performance on Facebook?\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- breakdown_type: 'age', 'gender', 'placement', 'device', or 'all' (default)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 1-3 seconds (cached database query)\nData source: Cached database (aggregate metrics only - detailed breakdown requires live API)\n\nCommon insights:\n- Meta audiences typically skew mobile (70-85%)\n- Instagram tends to perform better with 18-34 age groups\n- Facebook Feed often has highest reach but Stories may have better engagement\n- Reels placement growing rapidly in 2024-2025",
        "operationId": "execute_get_meta_audience_insights",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "breakdown_type": "all",
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta audience and placement insights",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "breakdown_type": {
                        "default": "all",
                        "description": "Type of breakdown: 'age', 'gender', 'placement', 'device', or 'all' (default). Use 'all' for comprehensive audience analysis.",
                        "title": "Breakdown Type",
                        "type": "string"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_meta_audience_insights)"
                  },
                  "success": true,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_meta_audience_insights",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about audience demographics, which placements perform best, device breakdown, or targeting optimization for...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_meta_audience_insights"
      }
    },
    "/api/v1/tools/get_meta_campaign_details/execute": {
      "post": {
        "description": "User wants to see detailed information about a specific Meta campaign, including its full structure (ad sets, ads, targeting, budgets).\n\nThis tool retrieves comprehensive details about a single campaign. With `include_hierarchy=true`, it shows the complete campaign tree: Campaign - Ad Sets - Ads.\n\nReturns:\n- Campaign settings (name, status, objective, budget, bid strategy, schedule)\n- Performance summary (impressions, clicks, spend, reach, CTR, CPC)\n- Ads Manager URL\n- When include_hierarchy=true: Full structure with all ad sets and their ads, targeting details, hierarchy stats\n\nWhen to use this tool:\n- \"Show me details for campaign [ID]\"\n- \"What's the structure of my campaign?\"\n- \"How many ad sets and ads does this campaign have?\"\n- \"What targeting is set on my campaign?\"\n- \"Show me the full campaign hierarchy\"\n- After `list_meta_campaigns` when user wants to drill into a specific campaign\n\nParameters:\n- campaign_id: The Meta Campaign ID (required)\n- include_hierarchy: Include ad sets and ads (default: false, set true for full view)\n\nExecution time: 3-10 seconds (longer with hierarchy due to multiple API calls)\nData source: Meta Marketing API (live)\n\nWorkflow:\n1. Use `list_meta_campaigns` to find campaign IDs\n2. Use `get_meta_campaign_details` with `include_hierarchy=true` to see everything\n3. Use `update_meta_ad_set` or `update_meta_ad` to make changes to specific items",
        "operationId": "execute_get_meta_campaign_details",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "include_hierarchy": false
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting detailed campaign information",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "description": "The Meta Campaign ID to get details for (required)",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "include_hierarchy": {
                        "default": false,
                        "description": "Include full hierarchy with ad sets and ads (default: false). Set to true to see the complete campaign structure.",
                        "title": "Include Hierarchy",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_meta_campaign_details)"
                  },
                  "success": true,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_meta_campaign_details",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see detailed information about a specific Meta campaign, including its full structure (ad sets, ads, ta...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_meta_campaign_details"
      }
    },
    "/api/v1/tools/get_meta_campaign_performance/execute": {
      "post": {
        "description": "User asks about Meta/Facebook/Instagram ad performance, campaign metrics, ROAS, spend analysis, or wants to understand how their Meta ads are performing.\n\nThis tool retrieves comprehensive campaign performance metrics from Meta Ads.\n\nReturns:\n- Account summary (total spend, impressions, reach, conversions, ROAS)\n- Campaign breakdown with status and objectives\n- Top performing campaigns by ROAS\n- Meta-specific metrics (reach, frequency, reach/impressions ratio)\n- Optimization recommendations\n\nWhen to use this tool:\n- \"How are my Meta ads performing?\"\n- \"What's my Facebook campaign ROAS?\"\n- \"Show me Instagram ad performance\"\n- \"Which Meta campaigns are doing best?\"\n- \"Analyze my Meta ad spend\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- include_recommendations: true (default) or false\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 1-3 seconds (cached database query)\nData source: campaign_daily_metrics table (updated nightly)\n\nNote: Unlike Google Ads, Meta does not have keyword or search term data. Meta uses interest-based targeting.",
        "operationId": "execute_get_meta_campaign_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "include_recommendations": true,
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta campaign performance analysis",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "include_recommendations": {
                        "default": true,
                        "description": "Include optimization recommendations. Default is true.",
                        "title": "Include Recommendations",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_meta_campaign_performance)"
                  },
                  "success": true,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_meta_campaign_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about Meta/Facebook/Instagram ad performance, campaign metrics, ROAS, spend analysis, or wants to understan...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_meta_campaign_performance"
      }
    },
    "/api/v1/tools/get_meta_lead_form_submissions/execute": {
      "post": {
        "description": "User wants to see lead submissions, lead data, or leads collected from a Meta lead form.\n\nRetrieves individual lead submissions for a specific lead form, including contact details and associated ad/campaign information.\n\nWhen to use this tool:\n- \"Show me the leads from form X\"\n- \"Get my lead form submissions\"\n- \"Download my Meta leads\"\n- \"Show lead data from my campaign\"\n- \"How many leads did I get?\"\n\nReturns per lead:\n- Submission timestamp\n- Field data (name, email, phone, etc. \u2014 varies by form)\n- Source: organic or paid (with campaign name and ad name)\n- Lead ID\n\nParameters:\n- form_id: Lead form ID (required \u2014 get from list_meta_lead_forms)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n- limit: Maximum leads to return (default: 100)\n\nPermission: 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.\n\nExecution time: 2-5 seconds\nData source: Meta Marketing API (live)",
        "operationId": "execute_get_meta_lead_form_submissions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "form_id": "string",
                  "limit": 100
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for retrieving lead form submissions.",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "form_id": {
                        "description": "Lead form ID (required). Get available form IDs from list_meta_lead_forms tool.",
                        "title": "Form Id",
                        "type": "string"
                      },
                      "limit": {
                        "default": 100,
                        "description": "Maximum number of lead submissions to return (default: 100)",
                        "title": "Limit",
                        "type": "integer"
                      }
                    },
                    "required": [
                      "form_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_meta_lead_form_submissions)"
                  },
                  "success": true,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_meta_lead_form_submissions",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_meta_lead_form_submissions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see lead submissions, lead data, or leads collected from a Meta lead form",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_meta_lead_form_submissions"
      }
    },
    "/api/v1/tools/get_monitor_history/execute": {
      "post": {
        "description": "Show trigger history for a monitoring alert.\n\n**What it does:**\n- Shows when the monitor triggered, what condition was met, and what value caused it\n- Shows which campaigns triggered and whether notifications were sent\n- Useful for understanding monitor behavior and verifying it works correctly\n\n**When to use:**\n- \"Show history for my CPA monitor\"\n- \"When did this alert last trigger?\"\n- \"What campaigns triggered my ROAS alert?\"\n\nRequires the monitor's alert_id (get from list_monitors).",
        "operationId": "execute_get_monitor_history",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "alert_id": "string",
                  "limit": 1
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for getting monitor trigger history.",
                    "properties": {
                      "alert_id": {
                        "description": "ID of the monitoring alert to get history for",
                        "title": "Alert Id",
                        "type": "string"
                      },
                      "limit": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Maximum number of trigger records to return. If not specified, returns all.",
                        "title": "Limit"
                      }
                    },
                    "required": [
                      "alert_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_monitor_history)"
                  },
                  "success": true,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_monitor_history",
                  "is_error": true,
                  "success": false,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_monitor_history"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Show trigger history for a monitoring alert",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_monitor_history"
      }
    },
    "/api/v1/tools/get_pmax_audience_signals/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGet current audience signals for a Performance Max campaign.\n\nReturns audience signal resource names and associated audience resource references.\n\n**Use when:**\n- User wants to see what audience signals are on their PMax campaign\n- Before adding new signals\n- To get resource_name for removal\n\n**Parameters:**\n- campaign_id: The PMax campaign ID\n\n**Execution time:** 1-2 seconds",
        "operationId": "execute_get_pmax_audience_signals",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting audience signals from a PMax campaign.",
                    "properties": {
                      "campaign_id": {
                        "description": "The Google Ads campaign ID (numeric). Example: '21854471508'",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_pmax_audience_signals)"
                  },
                  "success": true,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_pmax_audience_signals",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_audience_signals"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_pmax_audience_signals"
      }
    },
    "/api/v1/tools/get_pmax_search_themes/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGet current search themes for a Performance Max campaign.\n\nReturns each search theme's text and approval_status (APPROVED, LIMITED, DISAPPROVED, UNDER_REVIEW).\n\n**Use when:**\n- User wants to see what search themes are set on their PMax campaign\n- Before adding new themes (to check current count \u2014 max 50)\n- To verify theme approval status after adding\n\n**Parameters:**\n- campaign_id: The PMax campaign ID (get from list_campaigns)\n\n**Execution time:** 1-2 seconds",
        "operationId": "execute_get_pmax_search_themes",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting search themes from a PMax campaign.",
                    "properties": {
                      "campaign_id": {
                        "description": "The Google Ads campaign ID (numeric). Example: '21854471508'",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_pmax_search_themes)"
                  },
                  "success": true,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_pmax_search_themes",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_pmax_search_themes"
      }
    },
    "/api/v1/tools/get_research_status/execute": {
      "post": {
        "description": "Check the status of a research job.\n\n**What it does:**\n- Shows current progress percentage\n- Indicates if job is pending, in progress, completed, or failed\n- Provides summary when complete\n\n**When to use:**\n- \"Check on my research\"\n- \"Is my analysis done?\"\n- \"What's the status of job [ID]?\"",
        "operationId": "execute_get_research_status",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "job_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for getting research job status.",
                    "properties": {
                      "job_id": {
                        "description": "ID of the research job",
                        "title": "Job Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "job_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_research_status)"
                  },
                  "success": true,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_research_status",
                  "is_error": true,
                  "success": false,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_research_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Check the status of a research job",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_research_status"
      }
    },
    "/api/v1/tools/get_tiktok_ad_performance/execute": {
      "post": {
        "description": "Get TikTok ad-level performance with creative details, video metrics, and engagement.\n\nReturns: per-ad metrics (spend, CTR, ROAS, hook rate, completion rate, engagement rate), creative metadata (ad name, format, video ID), and top/underperforming ads.\n\nWhen to use: \"Show my TikTok ad performance\", \"Which TikTok ads are best?\", \"TikTok creative performance\"\n\nParameters:\n- lookback_days: default 30\n- limit: max ads to return (1-50, default 20)\n- offset: pagination offset",
        "operationId": "execute_get_tiktok_ad_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "end_date": "string",
                  "limit": 20,
                  "lookback_days": 30,
                  "offset": 0,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD",
                        "title": "End Date"
                      },
                      "limit": {
                        "default": 20,
                        "description": "Max ads to return (1-50)",
                        "title": "Limit",
                        "type": "integer"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "offset": {
                        "default": 0,
                        "description": "Pagination offset",
                        "title": "Offset",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_tiktok_ad_performance)"
                  },
                  "success": true,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_tiktok_ad_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_ad_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get TikTok ad-level performance with creative details, video metrics, and engagement",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_tiktok_ad_performance"
      }
    },
    "/api/v1/tools/get_tiktok_audience_insights/execute": {
      "post": {
        "description": "Analyze TikTok audience segment performance by age, gender, and combined demographics.\n\nReturns: age group breakdown (AGE_18_24 to AGE_55_100), gender breakdown, best/worst segments, saturation detection, targeting recommendations.\n\nNote: TikTok audience API doesn't return conversion values per demographic \u2014 segments ranked by CPA or CTR.\n\nWhen to use: \"TikTok audience performance\", \"Which age group works best on TikTok?\", \"TikTok demographic breakdown\"\n\nParameters:\n- breakdown_type: 'age', 'gender', 'age_gender', or 'all' (default)\n- include_saturation: true/false (default true)",
        "operationId": "execute_get_tiktok_audience_insights",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "breakdown_type": "all",
                  "end_date": "string",
                  "include_saturation": true,
                  "lookback_days": 30,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "breakdown_type": {
                        "default": "all",
                        "description": "Breakdown: age, gender, age_gender, or all",
                        "title": "Breakdown Type",
                        "type": "string"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD",
                        "title": "End Date"
                      },
                      "include_saturation": {
                        "default": true,
                        "description": "Include saturation trend analysis",
                        "title": "Include Saturation",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_tiktok_audience_insights)"
                  },
                  "success": true,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_tiktok_audience_insights",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_audience_insights"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Analyze TikTok audience segment performance by age, gender, and combined demographics",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_tiktok_audience_insights"
      }
    },
    "/api/v1/tools/get_tiktok_campaign_details/execute": {
      "post": {
        "description": "Get detailed information about a specific TikTok campaign including status, budget, objective, and timestamps.",
        "operationId": "execute_get_tiktok_campaign_details",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for getting TikTok campaign details",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "campaign_id": {
                        "description": "TikTok campaign ID",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_tiktok_campaign_details)"
                  },
                  "success": true,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_tiktok_campaign_details",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_details"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get detailed information about a specific TikTok campaign including status, budget, objective, and timestamps",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_tiktok_campaign_details"
      }
    },
    "/api/v1/tools/get_tiktok_campaign_performance/execute": {
      "post": {
        "description": "Get TikTok campaign performance metrics including TikTok-specific video and engagement data.\n\nReturns: account summary (spend, impressions, reach, conversions, ROAS), campaign breakdown with video metrics (hook rate, completion rate), engagement metrics (likes, shares, comments), and recommendations.\n\nWhen to use: \"How are my TikTok campaigns performing?\", \"Show TikTok performance\", \"TikTok ROAS\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90\n- start_date/end_date: Optional YYYY-MM-DD (overrides lookback_days)",
        "operationId": "execute_get_tiktok_campaign_performance",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "end_date": "string",
                  "include_recommendations": true,
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD (overrides lookback_days)",
                        "title": "End Date"
                      },
                      "include_recommendations": {
                        "default": true,
                        "description": "Include optimization recommendations",
                        "title": "Include Recommendations",
                        "type": "boolean"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze: 7, 14, 30, 60, or 90",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD (overrides lookback_days)",
                        "title": "Start Date"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_tiktok_campaign_performance)"
                  },
                  "success": true,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_tiktok_campaign_performance",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_tiktok_campaign_performance"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get TikTok campaign performance metrics including TikTok-specific video and engagement data",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_tiktok_campaign_performance"
      }
    },
    "/api/v1/tools/get_usage_status/execute": {
      "post": {
        "description": "Get your current usage status with interactive quota widget.\n\nThis tool shows your tool call usage for the current billing period and provides upgrade options if needed.\n\n**Returns:**\n- Current usage (calls used / limit)\n- Subscription tier (Free, Plus, Pro, Enterprise)\n- Days until quota reset\n- Upgrade options with pricing\n\n**Use this tool when:**\n- User asks \"how many calls do I have left?\"\n- User asks about their subscription or quota\n- User wants to check their usage\n- User asks about upgrading their plan\n\n**Widget Display (ChatGPT):**\nIn ChatGPT, this tool displays an interactive widget with:\n- Visual progress bar showing usage\n- Upgrade buttons that open Stripe checkout\n- Plan comparison with pricing\n\n**Note:** This tool is READ-ONLY and safe to call anytime.",
        "operationId": "execute_get_usage_status",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {}
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for getting usage status with quota widget",
                    "properties": {},
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for get_usage_status)"
                  },
                  "success": true,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: get_usage_status",
                  "is_error": true,
                  "success": false,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "get_usage_status"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get your current usage status with interactive quota widget",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "get_usage_status"
      }
    },
    "/api/v1/tools/help_user_upload/execute": {
      "post": {
        "description": "Show user instructions for uploading images to postimages.org for Performance Max campaigns.\n\n\u26a0\ufe0f CALL THIS FIRST when user wants to create a PMax campaign!\n\n**YOUR ROLE**: Image Upload Guide\n\n**WHEN TO USE**:\n- User says \"create PMax campaign\" or similar\n- Before asking for image uploads\n- Anytime user needs help uploading images\n\n**WHAT THIS DOES**:\n- Returns clear, step-by-step instructions\n- Tells user to upload to postimages.org\n- Explains how to get Direct links (not share pages)\n- Shows example URL format\n\n**DO NOT**:\n- Ask user to upload via ChatGPT's paperclip (won't work with size limits!)\n- Request base64 data (too large!)\n- Skip this step (user needs clear guidance)\n\n**AFTER THIS**:\n- User uploads to postimages.org\n- User pastes Direct links in chat\n- You call validate_and_prepare_assets with those URLs\n\n**Execution Time**: <1 second (just returns text)\n**Authentication**: Not required",
        "operationId": "execute_help_user_upload",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {}
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for help_user_upload tool (no parameters needed).",
                    "properties": {},
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for help_user_upload)"
                  },
                  "success": true,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: help_user_upload",
                  "is_error": true,
                  "success": false,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "help_user_upload"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Show user instructions for uploading images to postimages",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "help_user_upload"
      }
    },
    "/api/v1/tools/infer_business_profile/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nAutomatically infer business profile from campaign data using AI analysis.\n\n\u26a0\ufe0f IMPORTANT: This tool ANALYZES data but may SAVE a profile if confidence is high.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1 Feature 5):**\n- Analyzes campaign names, keywords, and ad copy\n- Uses Claude AI to classify business type\n- Infers business vertical, size, goals, and audience\n- Returns confidence level (high, medium, low)\n- Auto-saves if confidence is HIGH, asks confirmation for LOW\n\n**Inference Process:**\n1. Collects campaign names and keywords from Google Ads\n2. Analyzes patterns (B2B vs B2C, product vs service)\n3. Uses AI to classify business vertical\n4. Estimates business size from ad spend\n5. Identifies primary goals from campaign types\n\n**Confidence Levels:**\n- **HIGH** (\u22650.8): Auto-saves profile, high certainty\n- **MEDIUM** (0.5-0.8): Saves profile, reasonable certainty\n- **LOW** (<0.5): Returns suggestion, asks user to confirm\n\n**Parameters:**\n- **force_save**: Set to true to save even low-confidence profiles\n- **customer_id**: Optional (uses connected account if omitted)\n\n**Returns:**\n- Inferred business profile (vertical, size, goal, audience)\n- Confidence level and reasoning\n- Whether profile was saved or needs confirmation\n- Suggested confirmation prompt for low-confidence results\n\n**Use this tool when:**\n- User doesn't have a business profile set\n- User asks \"analyze my business\"\n- You need business context but none exists\n- After user connects a new Google Ads account\n\n**Example Flow:**\n1. Call `get_business_profile` - returns no profile\n2. Call `infer_business_profile` - analyzes campaigns\n3. If HIGH confidence: Profile saved automatically\n4. If LOW confidence: Ask user to confirm with suggested prompt\n\n**Execution time:** 3-8 seconds (AI analysis + optional save)",
        "operationId": "execute_infer_business_profile",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "customer_id": "string",
                  "force_save": false
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for inferring business profile from campaign data",
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "force_save": {
                        "default": false,
                        "description": "If true, save the profile even if confidence is low. Default is false (requires user confirmation for low-confidence profiles).",
                        "title": "Force Save",
                        "type": "boolean"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for infer_business_profile)"
                  },
                  "success": true,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: infer_business_profile",
                  "is_error": true,
                  "success": false,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "infer_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "infer_business_profile"
      }
    },
    "/api/v1/tools/list_campaign_extensions/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nList all extensions (sitelinks, callouts, structured snippets) for a campaign.\n\nReturns a summary of all extension types configured on the campaign.\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.\n- customer_id: Optional Google Ads customer ID\n\n**Returns:**\n- Sitelinks: Clickable links with text, URL, and descriptions\n- Callouts: Non-clickable text highlights\n- Structured Snippets: Header + values combinations\n\n**Execution time:** 1-2 seconds (read-only)\n\n**When to use:**\n- User asks \"what extensions do I have?\"\n- Before adding extensions, check what already exists\n- Auditing campaign setup\n\n**Example:**\nUser: \"Show me the extensions on my campaign\"\nAgent:\n1. Uses list_campaigns to get campaign_id\n2. Uses list_campaign_extensions to see all extensions",
        "operationId": "execute_list_campaign_extensions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing all extensions on a campaign.\n\nReturns all sitelinks, callouts, and structured snippets for the campaign.",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to list extensions for. Get from list_campaigns.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_campaign_extensions)"
                  },
                  "success": true,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_campaign_extensions",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaign_extensions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_campaign_extensions"
      }
    },
    "/api/v1/tools/list_campaigns/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nList all Google Ads campaigns for the connected account.\n\n\u26a0\ufe0f CRITICAL: Call this tool BEFORE creating new campaigns to ask the user:\n\"Would you like to create a new campaign or update an existing one?\"\n\nThis tool retrieves READ-ONLY data. Safe to call multiple times.\n\n**Returns:**\n- List of all campaigns with IDs, names, status, type, budget\n- 30-day performance metrics for each campaign (impressions, clicks, cost, conversions)\n- Total campaign count\n\n**Parameters:**\n- status_filter: ENABLED, PAUSED, or ALL (optional, default: ALL)\n- campaign_type: SEARCH, PERFORMANCE_MAX, DISPLAY, SHOPPING, or ALL (optional, default: ALL)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 2-5 seconds (direct Google Ads API call)\n\n**Campaign Management Guidelines:**\n\nBEFORE Starting Any Campaign Work:\n1. ALWAYS use `list_campaigns` first\n2. Ask user: \"Would you like to create a new campaign or update an existing one?\"\n3. If updating, use `get_campaign_structure` to see full details\n\nUser Intent Mapping:\n| User Says | Agent Action |\n|-----------|--------------|\n| \"Create a campaign for X\" | List existing first, then ask create vs update |\n| \"Change the budget\" | Get structure, then use update tools |\n| \"Add more keywords\" | Get structure, then add keywords |\n| \"Update my ads\" | Get structure, then update ad content |\n| \"Pause the campaign\" | Use pause_campaign tool |\n\n**Use this tool to:**\n- View all existing campaigns before creating new ones\n- Find campaign IDs for update operations\n- Get a quick overview of account structure\n- Identify active vs paused campaigns",
        "operationId": "execute_list_campaigns",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_type": "string",
                  "customer_id": "string",
                  "status_filter": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing campaigns",
                    "properties": {
                      "campaign_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by type: SEARCH, PERFORMANCE_MAX, DISPLAY, SHOPPING, or ALL (default: ALL)",
                        "title": "Campaign Type"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "status_filter": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by status: ENABLED, PAUSED, or ALL (default: ALL)",
                        "title": "Status Filter"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_campaigns)"
                  },
                  "success": true,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_campaigns",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_campaigns"
      }
    },
    "/api/v1/tools/list_connected_accounts/execute": {
      "post": {
        "description": "List all connected ad accounts across platforms.\n\nReturns account IDs, names, and platforms for all active accounts. Use this at the start\nof every conversation to know which accounts are available.\n\nCRITICAL for multi-account users (agencies):\n- Always call this first to discover available accounts\n- Use the returned account IDs when calling any platform tool\n- If user mentions a business name, match it to an account from this list\n\nReturns for each account:\n- platform: google_ads, meta_ads, tiktok_ads, linkedin_ads\n- account_id: Platform account ID (pass to tools as customer_id/ad_account_id/advertiser_id/account_id)\n- account_name: Display name\n- account_tier: primary, secondary, or active\n- status: connected, needs_reauth\n\nZero API calls \u2014 reads directly from database.",
        "operationId": "execute_list_connected_accounts",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "platform": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for list_connected_accounts tool - no required params",
                    "properties": {
                      "platform": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by platform: google_ads, meta_ads, tiktok_ads, or linkedin_ads. If not specified, returns accounts across all platforms.",
                        "title": "Platform"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_connected_accounts)"
                  },
                  "success": true,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_connected_accounts",
                  "is_error": true,
                  "success": false,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_connected_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List all connected ad accounts across platforms [write]",
        "tags": [
          "general"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "list_connected_accounts"
      }
    },
    "/api/v1/tools/list_linkedin_campaign_groups/execute": {
      "post": {
        "description": "User wants to see their LinkedIn campaign groups (also called campaign folders or groups).\n\nList all campaign groups with their status, budget, and campaign count.\n\nParameters:\n- account_id: Optional LinkedIn Ad Account ID\n- status_filter: Filter by status (ACTIVE, PAUSED, ARCHIVED, or ALL)\n\nReturns:\n- All campaign groups with name, ID, status, budget, and campaign count\n\nExample Prompts:\n- \"Show me my LinkedIn campaign groups\"\n- \"List all campaign groups\"\n- \"What campaign groups do I have?\"\n- \"Show active campaign groups\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_list_linkedin_campaign_groups",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "status_filter": "ACTIVE,PAUSED,DRAFT"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing campaign groups",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "status_filter": {
                        "default": "ACTIVE,PAUSED,DRAFT",
                        "description": "Filter by status. Default: 'ACTIVE,PAUSED,DRAFT' (excludes deleted/removed clutter). Options: ACTIVE, PAUSED, DRAFT, ARCHIVED, or ALL (includes PENDING_DELETION, REMOVED).",
                        "title": "Status Filter",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_linkedin_campaign_groups)"
                  },
                  "success": true,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_linkedin_campaign_groups",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaign_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see their LinkedIn campaign groups (also called campaign folders or groups)",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_linkedin_campaign_groups"
      }
    },
    "/api/v1/tools/list_linkedin_campaigns/execute": {
      "post": {
        "description": "User wants to see all their LinkedIn campaigns with performance metrics.\n\nList all LinkedIn campaigns with summary metrics (clicks, impressions, cost, CTR).\n\nReturns:\n- All campaigns grouped by status (Active, Paused, Other)\n- 30-day performance metrics for each campaign\n- Campaign Manager links\n\nParameters:\n- account_id: Optional LinkedIn Ad Account ID\n- status_filter: Filter by status (ACTIVE, PAUSED, ALL)\n- limit: Maximum campaigns to return (default: 50)\n- lookback_days: Days for metrics (default: 30)\n\nExample Prompts:\n- \"Show me my LinkedIn campaigns\"\n- \"List all active LinkedIn campaigns\"\n- \"What are my LinkedIn campaign metrics?\"\n\nExecution time: 3-5 seconds",
        "operationId": "execute_list_linkedin_campaigns",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_group_id": "string",
                  "limit": 50,
                  "lookback_days": 30,
                  "status_filter": "ACTIVE,PAUSED,DRAFT"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing LinkedIn campaigns with metrics",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID (optional - uses default connected account)",
                        "title": "Account Id"
                      },
                      "campaign_group_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter campaigns by Campaign Group ID. Only returns campaigns that belong to this group. Format: numeric ID or urn:li:sponsoredCampaignGroup:XXXXX.",
                        "title": "Campaign Group Id"
                      },
                      "limit": {
                        "default": 50,
                        "description": "Maximum campaigns to return (default: 50, max: 100)",
                        "maximum": 100,
                        "minimum": 1,
                        "title": "Limit",
                        "type": "integer"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days for metrics (default: 30)",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "status_filter": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "ACTIVE,PAUSED,DRAFT",
                        "description": "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.",
                        "title": "Status Filter"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_linkedin_campaigns)"
                  },
                  "success": true,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_linkedin_campaigns",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see all their LinkedIn campaigns with performance metrics",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_linkedin_campaigns"
      }
    },
    "/api/v1/tools/list_linkedin_conversions/execute": {
      "post": {
        "description": "User wants to see available conversion tracking options.\n\nList all conversion rules for a LinkedIn ad account.\n\nReturns:\n- All conversions with ID, name, type, status\n- Attribution windows\n- Can be used to select conversions for campaigns\n\nExecution time: 2-3 seconds",
        "operationId": "execute_list_linkedin_conversions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing conversions",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_linkedin_conversions)"
                  },
                  "success": true,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_linkedin_conversions",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see available conversion tracking options",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_linkedin_conversions"
      }
    },
    "/api/v1/tools/list_linkedin_creatives/execute": {
      "post": {
        "description": "User wants to see all ads in a LinkedIn campaign.\n\nList all creatives/ads for a specific campaign.\n\nReturns:\n- All creatives with ID, status, review status\n- Text preview and CTA\n- Campaign ID\n\nExecution time: 2-3 seconds",
        "operationId": "execute_list_linkedin_creatives",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing creatives in a campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to list creatives for",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_linkedin_creatives)"
                  },
                  "success": true,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_linkedin_creatives",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_linkedin_creatives"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see all ads in a LinkedIn campaign",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_linkedin_creatives"
      }
    },
    "/api/v1/tools/list_meta_ad_sets/execute": {
      "post": {
        "description": "User wants to see the ad sets within a specific Meta campaign, including their targeting, budgets, and optimization settings.\n\nThis tool retrieves ad sets for a given campaign with their status, budget, optimization goal, and billing event.\n\nReturns:\n- Ad set list with name, ID, status, budget, optimization goal, billing event\n- Next step guidance for editing ad sets or viewing ads\n\nWhen to use this tool:\n- \"Show me the ad sets in campaign [ID]\"\n- \"What ad sets are running in my campaign?\"\n- \"List the ad groups for this campaign\"\n- \"Which ad sets are active/paused?\"\n- Before using `update_meta_ad_set` when user doesn't know the ad set ID\n\nParameters:\n- campaign_id: Campaign ID to list ad sets for (required)\n- status: Filter by status (optional)\n- limit: Max ad sets to return (default: 100)\n- ad_account_id: Optional\n\nExecution time: 2-5 seconds\nData source: Meta Marketing API (live)\n\nWorkflow:\n1. Use `list_meta_campaigns` to find campaign IDs\n2. Use `list_meta_ad_sets` with campaign_id to see ad sets\n3. Use `update_meta_ad_set` to edit targeting, budget, or placements\n4. Use `list_meta_ads` with ad_set_id to see individual ads",
        "operationId": "execute_list_meta_ad_sets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "campaign_id": "string",
                  "limit": 100,
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing Meta ad sets",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter ad sets to a specific campaign ID (optional - if not provided, lists all ad sets in the account)",
                        "title": "Campaign Id"
                      },
                      "limit": {
                        "default": 100,
                        "description": "Maximum number of ad sets to return (default: 100)",
                        "maximum": 500,
                        "minimum": 1,
                        "title": "Limit",
                        "type": "integer"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by status: comma-separated values like 'ACTIVE,PAUSED'",
                        "title": "Status"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_ad_sets)"
                  },
                  "success": true,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_ad_sets",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ad_sets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see the ad sets within a specific Meta campaign, including their targeting, budgets, and optimization s...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_ad_sets"
      }
    },
    "/api/v1/tools/list_meta_ads/execute": {
      "post": {
        "description": "User wants to see the individual ads within a specific Meta ad set, including their status and creative information.\n\nThis tool retrieves ads for a given ad set with their name, status, and creative ID.\n\nReturns:\n- Ad list with name, ID, status, creative ID\n- Next step guidance for editing ads\n\nWhen to use this tool:\n- \"Show me the ads in ad set [ID]\"\n- \"What ads are running in this ad set?\"\n- \"List the individual ads\"\n- \"Which ads are active/paused?\"\n- Before using `update_meta_ad` when user doesn't know the ad ID\n\nParameters:\n- ad_set_id: Ad Set ID to list ads for (required for direct listing)\n- campaign_id: Campaign ID (will guide user to use ad_set_id)\n- status: Filter by status (optional)\n- limit: Max ads to return (default: 100)\n\nExecution time: 2-5 seconds\nData source: Meta Marketing API (live)\n\nWorkflow:\n1. Use `list_meta_ad_sets` to find ad set IDs\n2. Use `list_meta_ads` with ad_set_id to see ads\n3. Use `update_meta_ad` to pause/resume or swap creative\n\nAlternative: Use `get_meta_campaign_details` with `include_hierarchy=true` to see the full campaign tree at once.",
        "operationId": "execute_list_meta_ads",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "ad_set_id": "string",
                  "campaign_id": "string",
                  "limit": 100,
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing Meta ads",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "ad_set_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter ads to a specific ad set ID",
                        "title": "Ad Set Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter ads to a specific campaign ID",
                        "title": "Campaign Id"
                      },
                      "limit": {
                        "default": 100,
                        "description": "Maximum number of ads to return (default: 100)",
                        "maximum": 500,
                        "minimum": 1,
                        "title": "Limit",
                        "type": "integer"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by status: comma-separated values like 'ACTIVE,PAUSED'",
                        "title": "Status"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_ads)"
                  },
                  "success": true,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_ads",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see the individual ads within a specific Meta ad set, including their status and creative information",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_ads"
      }
    },
    "/api/v1/tools/list_meta_campaigns/execute": {
      "post": {
        "description": "User wants to see their existing Meta/Facebook/Instagram campaigns, browse campaign structure, or find a campaign ID.\n\nThis tool retrieves all campaigns in the connected Meta ad account with their status, objective, budget, and creation date.\n\nReturns:\n- Campaign list with name, ID, status, objective, budget\n- Status summary (how many ACTIVE, PAUSED, etc.)\n- Next step guidance for drilling into campaign details\n\nWhen to use this tool:\n- \"Show me my Meta campaigns\"\n- \"List my Facebook ad campaigns\"\n- \"What campaigns do I have running?\"\n- \"Which campaigns are active?\"\n- \"Find my campaign for [product/brand]\"\n- \"I need to find a campaign ID\"\n- Before using update/pause/resume tools when user doesn't know the campaign ID\n\nParameters:\n- status: Filter by status (comma-separated: 'ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED')\n- effective_status: Filter by effective status (includes inherited states like 'CAMPAIGN_PAUSED')\n- objective: Filter by objective (OUTCOME_TRAFFIC, OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS)\n- limit: Max campaigns to return (default: 100)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 2-5 seconds\nData source: Meta Marketing API (live)\n\nWorkflow:\n1. Use `list_meta_campaigns` to find campaigns\n2. Use `get_meta_campaign_details` with a campaign ID to see full structure\n3. Use `update_meta_campaign` or `update_meta_ad_set` to make changes",
        "operationId": "execute_list_meta_campaigns",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "effective_status": "string",
                  "limit": 100,
                  "objective": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing Meta campaigns",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "effective_status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by effective status: comma-separated values. Options: ACTIVE, PAUSED, DELETED, ARCHIVED, IN_PROCESS, WITH_ISSUES, CAMPAIGN_PAUSED",
                        "title": "Effective Status"
                      },
                      "limit": {
                        "default": 100,
                        "description": "Maximum number of campaigns to return (default: 100, max: 500)",
                        "maximum": 500,
                        "minimum": 1,
                        "title": "Limit",
                        "type": "integer"
                      },
                      "objective": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by campaign objective: OUTCOME_TRAFFIC, OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS, OUTCOME_ENGAGEMENT",
                        "title": "Objective"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by status: comma-separated values like 'ACTIVE,PAUSED'. Options: ACTIVE, PAUSED, DELETED, ARCHIVED",
                        "title": "Status"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_campaigns)"
                  },
                  "success": true,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_campaigns",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see their existing Meta/Facebook/Instagram campaigns, browse campaign structure, or find a campaign ID",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_campaigns"
      }
    },
    "/api/v1/tools/list_meta_custom_audiences/execute": {
      "post": {
        "description": "User wants to browse, list, or select Custom Audiences for targeting \u2014 DB lists, lookalike audiences, remarketing segments, website visitors, engagement audiences.\n\nReturns all Custom Audiences for the ad account with ID, name, type (subtype), approximate size, and delivery status.\n\nWorkflow:\n1. Call this tool to discover available custom audiences\n2. Use audience IDs with campaign creation tools via `custom_audiences` parameter\n3. Optionally use `excluded_custom_audiences` to exclude specific audiences\n\nWhen to use:\n- Before creating campaigns that need custom audience targeting\n- When user asks \"which audiences do I have?\" or \"show me my lookalike audiences\"\n- When setting up remarketing or DB-list campaigns",
        "operationId": "execute_list_meta_custom_audiences",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing custom audiences in a Meta ad account",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_custom_audiences)"
                  },
                  "success": true,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_custom_audiences",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_custom_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to browse, list, or select Custom Audiences for targeting \u2014 DB lists, lookalike audiences, remarketing seg...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_custom_audiences"
      }
    },
    "/api/v1/tools/list_meta_instagram_accounts/execute": {
      "post": {
        "description": "User wants to run ads on Instagram, asks about Instagram accounts, or you need to find the instagram_account_id before campaign creation.\n\nReturns Instagram accounts that are:\n1. Authorized for ads on the ad account (from Business Manager)\n2. Linked to Facebook Pages (from Page settings)\n\nWorkflow:\n1. Call this tool to discover available Instagram accounts\n2. Use the Instagram account ID with campaign creation tools via `instagram_account_id` parameter\n\nWhen to use:\n- Before any campaign creation to enable Instagram placements\n- When user asks \"which Instagram accounts can I use?\"\n- When an Instagram association fails and user needs to find the right account",
        "operationId": "execute_list_meta_instagram_accounts",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing Instagram accounts authorized for ads",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta ad account ID (e.g., 'act_123456'). If not provided, uses the connected account.",
                        "title": "Ad Account Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_instagram_accounts)"
                  },
                  "success": true,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_instagram_accounts",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_instagram_accounts"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to run ads on Instagram, asks about Instagram accounts, or you need to find the instagram_account_id befor...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_instagram_accounts"
      }
    },
    "/api/v1/tools/list_meta_lead_forms/execute": {
      "post": {
        "description": "User wants to see their Meta lead generation forms, list lead forms, or find a lead form ID.\n\nLists all lead generation forms for a Facebook Page associated with the ad account.\n\nWhen to use this tool:\n- \"Show me my Meta lead forms\"\n- \"List my Facebook lead generation forms\"\n- \"What lead forms do I have?\"\n- \"Find my lead form ID\"\n- \"Show lead forms for my page\"\n\nReturns per form:\n- Form name, status (ACTIVE/ARCHIVED), leads count\n- Creation date\n- Form questions (field names and types)\n- Form ID (needed for get_meta_lead_form_submissions and campaign creation with lead_form_id)\n\nParameters:\n- page_id: Facebook Page ID (optional \u2014 auto-resolved from ad account if not provided)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n- limit: Maximum forms to return (default: 50)\n\nNote: 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.\n\nPermission: 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.\n\nExecution time: 2-5 seconds\nData source: Meta Marketing API (live)",
        "operationId": "execute_list_meta_lead_forms",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "limit": 50,
                  "page_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing Meta lead generation forms.",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "limit": {
                        "default": 50,
                        "description": "Maximum number of lead forms to return (default: 50)",
                        "title": "Limit",
                        "type": "integer"
                      },
                      "page_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Facebook Page ID that owns the lead forms. If not provided, auto-resolves from the ad account's promotable pages.",
                        "title": "Page Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_lead_forms)"
                  },
                  "success": true,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_lead_forms",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_lead_forms"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to see their Meta lead generation forms, list lead forms, or find a lead form ID",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_lead_forms"
      }
    },
    "/api/v1/tools/list_meta_pixels/execute": {
      "post": {
        "description": "User wants conversion tracking, asks about Meta Pixels, or before creating OUTCOME_SALES campaigns.\n\nReturns all Meta Pixels for the ad account with their status and last fired time.\n\nWorkflow:\n1. Call this tool to discover available pixels\n2. Use the pixel ID with campaign creation tools via `pixel_id` parameter\n3. Optionally specify `pixel_event_name` (default: PURCHASE)\n\nWhen to use:\n- Before creating OUTCOME_SALES campaigns (pixel_id is required for conversion tracking)\n- When user asks \"which pixels do I have?\"\n- When setting up conversion tracking for campaigns",
        "operationId": "execute_list_meta_pixels",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for listing Meta Pixels",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta ad account ID (e.g., 'act_123456'). If not provided, uses the connected account.",
                        "title": "Ad Account Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_meta_pixels)"
                  },
                  "success": true,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_meta_pixels",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_meta_pixels"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants conversion tracking, asks about Meta Pixels, or before creating OUTCOME_SALES campaigns",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_meta_pixels"
      }
    },
    "/api/v1/tools/list_monitors/execute": {
      "post": {
        "description": "List all your monitoring alerts.\n\n**What it does:**\n- Shows all active and paused monitors\n- Displays conditions, trigger counts, and status\n- Provides alert IDs for management\n\n**When to use:**\n- \"Show my monitoring alerts\"\n- \"What am I monitoring?\"\n- \"List all my alerts\"",
        "operationId": "execute_list_monitors",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for listing monitoring alerts.",
                    "properties": {
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by status: 'active', 'paused', or 'all'",
                        "title": "Status"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_monitors)"
                  },
                  "success": true,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_monitors",
                  "is_error": true,
                  "success": false,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_monitors"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List all your monitoring alerts",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_monitors"
      }
    },
    "/api/v1/tools/list_pending_actions/execute": {
      "post": {
        "description": "List auto-actions waiting for your approval.\n\n**What it does:**\n- Shows pending campaign actions (pause, budget changes) queued by your monitors\n- Displays before/after state for each action\n- Shows expiry time (actions expire after 48 hours)\n\n**When to use:**\n- \"Show my pending actions\"\n- \"What actions need approval?\"\n- \"Any pending budget changes?\"\n\nAfter reviewing, use `manage_action` to approve or reject each action.",
        "operationId": "execute_list_pending_actions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "status": "pending_approval"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for listing pending auto-actions.",
                    "properties": {
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "pending_approval",
                        "description": "Filter: 'pending_approval', 'executed', 'rejected', 'expired', 'all'",
                        "title": "Status"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_pending_actions)"
                  },
                  "success": true,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_pending_actions",
                  "is_error": true,
                  "success": false,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_pending_actions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List auto-actions waiting for your approval",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_pending_actions"
      }
    },
    "/api/v1/tools/list_scheduled_tasks/execute": {
      "post": {
        "description": "List all your scheduled automation tasks.\n\n**What it does:**\n- Shows all scheduled briefs, monitors, and research jobs\n- Displays status, schedule, and last execution\n- Provides task IDs for management\n\n**When to use:**\n- \"Show my scheduled tasks\"\n- \"What automations do I have running?\"\n- \"List my scheduled reports\"",
        "operationId": "execute_list_scheduled_tasks",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "status": "string",
                  "task_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for listing scheduled tasks.",
                    "properties": {
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by status: 'active', 'paused', or 'all'",
                        "title": "Status"
                      },
                      "task_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by type: 'performance_brief', 'monitoring', 'research'",
                        "title": "Task Type"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_scheduled_tasks)"
                  },
                  "success": true,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_scheduled_tasks",
                  "is_error": true,
                  "success": false,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_scheduled_tasks"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List all your scheduled automation tasks",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_scheduled_tasks"
      }
    },
    "/api/v1/tools/list_tiktok_ad_groups/execute": {
      "post": {
        "description": "List TikTok ad groups. Optionally filter by campaign ID.",
        "operationId": "execute_list_tiktok_ad_groups",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "campaign_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for listing TikTok ad groups",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by campaign ID (optional)",
                        "title": "Campaign Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_tiktok_ad_groups)"
                  },
                  "success": true,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_tiktok_ad_groups",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ad_groups"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List TikTok ad groups",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_tiktok_ad_groups"
      }
    },
    "/api/v1/tools/list_tiktok_ads/execute": {
      "post": {
        "description": "List TikTok ads. Optionally filter by ad group ID or campaign ID.",
        "operationId": "execute_list_tiktok_ads",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "adgroup_id": "string",
                  "advertiser_id": "string",
                  "campaign_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for listing TikTok ads",
                    "properties": {
                      "adgroup_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by ad group ID (optional)",
                        "title": "Adgroup Id"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Filter by campaign ID (optional)",
                        "title": "Campaign Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_tiktok_ads)"
                  },
                  "success": true,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_tiktok_ads",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_ads"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List TikTok ads",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_tiktok_ads"
      }
    },
    "/api/v1/tools/list_tiktok_campaigns/execute": {
      "post": {
        "description": "List all TikTok campaigns with their status, objective, and budget.",
        "operationId": "execute_list_tiktok_campaigns",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for listing TikTok campaigns",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for list_tiktok_campaigns)"
                  },
                  "success": true,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: list_tiktok_campaigns",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "list_tiktok_campaigns"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List all TikTok campaigns with their status, objective, and budget",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "list_tiktok_campaigns"
      }
    },
    "/api/v1/tools/manage_action/execute": {
      "post": {
        "description": "Approve or reject a pending auto-action.\n\n**What it does:**\n- Approves a pending action \u2192 executes it immediately on the ad platform\n- Rejects a pending action \u2192 cancels it, no changes made\n\n**When to use:**\n- \"Approve action [action_id]\"\n- \"Reject action [action_id]\"\n- \"Approve all pending actions\" (call multiple times)\n\n**IMPORTANT:**\n- Approved actions are EXECUTED IMMEDIATELY (campaign paused, budget changed)\n- This is irreversible in the moment \u2014 verify before approving\n- Actions expire after 48 hours if not approved",
        "operationId": "execute_manage_action",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "action_id": "string",
                  "decision": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for approving or rejecting a pending action.",
                    "properties": {
                      "action_id": {
                        "description": "ID of the pending action to approve or reject",
                        "title": "Action Id",
                        "type": "string"
                      },
                      "decision": {
                        "description": "'approve' or 'reject'",
                        "title": "Decision",
                        "type": "string"
                      }
                    },
                    "required": [
                      "action_id",
                      "decision"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for manage_action)"
                  },
                  "success": true,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: manage_action",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_action"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Approve or reject a pending auto-action [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "manage_action"
      }
    },
    "/api/v1/tools/manage_linkedin_conversions/execute": {
      "post": {
        "description": "User wants to manage LinkedIn conversion tracking - list, create, associate conversions, or set up full conversion tracking.\n\nAll-in-one conversion management tool with 4 actions:\n\nActions:\n- `list`: Show all conversions for the account\n- `create`: Create a new conversion rule\n- `associate`: Link a conversion to a campaign\n- `setup`: Full setup - create conversion + associate with campaign in one step\n\nParameters:\n- action: 'list', 'create', 'associate', or 'setup' (required)\n- name: Conversion name (for 'create' and 'setup')\n- type: Conversion type - LEAD, PURCHASE, SIGN_UP, KEY_PAGE_VIEW, etc. (default: LEAD)\n- url: URL for conversion rule matching\n- value: Fixed conversion value in USD\n- conversion_id: Conversion ID (for 'associate')\n- campaign_id: Campaign ID (for 'associate' and 'setup')\n- landing_page_url: Landing page URL (for 'setup')\n- account_id: Optional LinkedIn Ad Account ID\n\nExample Prompts:\n- \"Show me my LinkedIn conversions\"\n- \"Set up conversion tracking for my campaign\"\n- \"Create a lead conversion for sign-ups\"\n- \"Associate conversion 123 with campaign 456\"\n- \"Track purchases on my landing page\"\n- \"Help me set up LinkedIn conversion tracking\"\n\nExecution time: 2-5 seconds",
        "operationId": "execute_manage_linkedin_conversions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "action": "string",
                  "campaign_id": "string",
                  "conversion_id": "string",
                  "name": "string",
                  "type": "LEAD",
                  "url": "string",
                  "value": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for conversion tracking management",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "action": {
                        "description": "Action: 'list', 'create', 'associate', or 'setup'",
                        "title": "Action",
                        "type": "string"
                      },
                      "campaign_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Campaign ID (for 'associate' and 'setup' actions)",
                        "title": "Campaign Id"
                      },
                      "conversion_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Conversion ID (for 'associate' action)",
                        "title": "Conversion Id"
                      },
                      "landing_page_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Landing page URL (for 'setup' action)",
                        "title": "Landing Page Url"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Conversion name (for 'create' action)",
                        "title": "Name"
                      },
                      "type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "LEAD",
                        "description": "Conversion type: LEAD, PURCHASE, SIGN_UP, KEY_PAGE_VIEW, etc.",
                        "title": "Type"
                      },
                      "url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "URL for conversion rule matching",
                        "title": "Url"
                      },
                      "value": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Fixed conversion value",
                        "title": "Value"
                      }
                    },
                    "required": [
                      "action"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for manage_linkedin_conversions)"
                  },
                  "success": true,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: manage_linkedin_conversions",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_linkedin_conversions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to manage LinkedIn conversion tracking - list, create, associate conversions, or set up full conversion tr... [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "manage_linkedin_conversions"
      }
    },
    "/api/v1/tools/manage_scheduled_task/execute": {
      "post": {
        "description": "Manage a scheduled task \u2014 pause, resume, or delete. Works for briefs, monitors, and all task types.\n\n**What it does:**\n- Pause: Temporarily stop a scheduled task or monitoring alert\n- Resume: Restart a paused task or monitor\n- Delete: Permanently remove a task or monitor\n\n**When to use:**\n- \"Pause my daily brief\"\n- \"Resume my performance reports\"\n- \"Delete the weekly summary task\"\n- \"Pause this monitoring alert\"\n\n**Required parameters:**\n- `task_id`: The ID of the task (get from `list_scheduled_tasks` or `list_monitors`)\n- `action`: 'pause', 'resume', or 'delete'\n\nTo delete a monitoring alert specifically, you can also use `delete_monitor`.",
        "operationId": "execute_manage_scheduled_task",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "action": "string",
                  "task_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for managing a scheduled task.",
                    "properties": {
                      "action": {
                        "description": "Action to take: 'pause', 'resume', 'delete'",
                        "title": "Action",
                        "type": "string"
                      },
                      "task_id": {
                        "description": "ID of the scheduled task",
                        "title": "Task Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "task_id",
                      "action"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for manage_scheduled_task)"
                  },
                  "success": true,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: manage_scheduled_task",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "manage_scheduled_task"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Manage a scheduled task \u2014 pause, resume, or delete [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "manage_scheduled_task"
      }
    },
    "/api/v1/tools/optimize_budget_allocation/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nOptimize budget allocation across campaigns using linear programming to maximize conversions.\n\n\u26a0\ufe0f IMPORTANT: This tool retrieves READ-ONLY optimization recommendations. Safe to call multiple times. Does NOT automatically change budgets.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1):**\n- Uses linear programming to optimize budget distribution\n- Maximizes total conversions while respecting constraints\n- Provides current vs optimized allocation comparison\n- Categorizes campaigns into actions: PAUSE/SCALE/REDUCE/MAINTAIN\n- Shows expected conversion lift from optimization\n- Generates specific recommendations with budget amounts\n\n**Returns detailed optimization plan:**\n- Current allocation (what you have now)\n- Optimized allocation (what you should have)\n- Expected conversion lift (absolute and percentage)\n- Campaign-by-campaign actions with reasoning\n- Budget change amounts and percentages\n- Specific implementation recommendations\n\n**Optimization Algorithm:**\nUses scipy linear programming with constraints:\n1. Sum of budgets = total_budget (you don't overspend)\n2. Only campaigns with ROAS >= target get significant budget\n3. Min budget per campaign >= min_daily_budget (or $0 to pause)\n4. Max change per campaign <= \u00b1max_change_percentage (avoid drastic shifts)\n\n**Target ROAS Resolution (3-tier priority):**\n1. User override (if target_roas parameter provided)\n2. Account goals table (user-set or API-pulled)\n3. 90-day historical average ROAS\n4. Default to 1.0x (breakeven) if no data\n\n**Configuration Parameters:**\n- **total_budget**: Total monthly budget to allocate (REQUIRED)\n- **lookback_days**: 7, 30, 60, 90, or 120 days (default: 30)\n- **start_date**: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- **end_date**: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n- **target_roas**: Optional override (e.g., 3.0 for 3.0x)\n- **max_change_percentage**: 0.0-1.0 (default: 0.5 = \u00b150%)\n  * 0.3 = Conservative (\u00b130% change, minimal disruption)\n  * 0.5 = Balanced (\u00b150% change, standard optimization)\n  * 0.7 = Aggressive (\u00b170% change, fast scaling)\n- **min_daily_budget**: Minimum $ per campaign (default: $5.00, or $0.00 to allow pausing)\n- **customer_id**: Optional (uses connected account if omitted)\n\n**Execution time:** 1-5 seconds (depends on campaign count)\n**Data source:** campaign_daily_metrics table (updated nightly)\n\n**Use this tool when:**\n- User wants to optimize budget allocation\n- User asks \"how should I allocate my budget?\"\n- User wants to maximize conversions with current spend\n- User wants data-driven budget recommendations\n- After running wasted spend analysis (natural next step)\n\n\ud83d\udcca **AFTER calling this tool, help the user understand:**\n\n**Campaign Actions:**\n- **PAUSE**: Campaigns below target ROAS, losing money (ROAS < target)\n- **SCALE**: High performers, increase budget by X% (top conversion rates)\n- **REDUCE**: Underperformers, decrease budget by X% (low efficiency)\n- **MAINTAIN**: Steady performers, keep current budget (\u00b15% change)\n\n**Expected Impact:**\n- Current conversions: What you get now\n- Optimized conversions: What you could get\n- Conversion lift: Additional conversions (+X%)\n\n**Example Interpretation:**\n\"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.\"\n\n**Implementation Steps:**\n1. Review recommended changes carefully\n2. Start with campaigns marked CRITICAL (pause/scale first)\n3. Apply changes gradually if user is risk-averse\n4. Monitor performance for 7-14 days after changes\n5. Re-run optimization monthly for continuous improvement\n\n**Important Notes:**\n- This is a RECOMMENDATION tool, not automated budget application\n- User must review and apply changes manually in Google Ads\n- Avoid large changes (>50%) for campaigns in learning phase (<7 days)\n- Consider seasonality when interpreting results\n- Re-optimize every 30 days as performance changes\n\n**Visualization Tip:**\nFor 5+ campaigns, suggest creating a grouped bar chart showing current vs optimized budgets side-by-side.\n\n**Best Practices:**\n- Start conservative (max_change_percentage=0.3) for first optimization\n- Increase aggressiveness (0.5-0.7) as you gain confidence\n- Use longer lookback_days (60-90) during seasonal changes\n- Set realistic target_roas (start with 1.0x breakeven, increase gradually)\n\n\ud83d\udcac **Community**: For optimization discussions, visit our Discord: https://discord.gg/dH3Qt4YS",
        "operationId": "execute_optimize_budget_allocation",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string",
                  "target_roas": 1.0,
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for budget allocation optimization",
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze for historical performance (7, 30, 60, 90, or 120 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "max_change_percentage": {
                        "default": 0.5,
                        "description": "Maximum budget change per campaign as a percentage (0.0-1.0). Default 0.5 = \u00b150%. Use 0.3 for conservative, 0.7 for aggressive.",
                        "title": "Max Change Percentage",
                        "type": "number"
                      },
                      "min_daily_budget": {
                        "default": 5.0,
                        "description": "Minimum daily budget per campaign in dollars. Default is $5.00. Set to 0.0 to allow complete pausing of underperformers.",
                        "title": "Min Daily Budget",
                        "type": "number"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average.",
                        "title": "Target Roas"
                      },
                      "total_budget": {
                        "description": "Total monthly budget to allocate across campaigns (e.g., 15000.00 for $15K/month)",
                        "title": "Total Budget",
                        "type": "number"
                      }
                    },
                    "required": [
                      "total_budget"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for optimize_budget_allocation)"
                  },
                  "success": true,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: optimize_budget_allocation",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_budget_allocation"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "optimize_budget_allocation"
      }
    },
    "/api/v1/tools/optimize_linkedin_budget/execute": {
      "post": {
        "description": "User asks how to allocate their LinkedIn budget,\nwants budget optimization recommendations, or asks \"How should I split my LinkedIn spend?\"\n\nUses linear programming to optimize budget allocation across campaigns:\n\nOptimization Process:\n1. Analyzes historical campaign performance\n2. Calculates efficiency scores (conversions or leads per dollar)\n3. Uses scipy linear programming to find optimal allocation\n4. Respects constraints (max change %, min budget, total budget)\n\nReturns:\n- Current vs Optimized budget allocation\n- Expected impact (conversion/lead lift)\n- Campaigns to SCALE, MAINTAIN, REDUCE, or PAUSE\n- Projected improvement percentage\n\nConstraints Applied:\n- Total budget = user specified amount\n- Max change per campaign (default 50%)\n- Min daily budget (LinkedIn minimum $10, default $20)\n- Only allocate to profitable campaigns (ROAS > 1.0)\n\nParameters:\n- total_budget: Total monthly budget to allocate (required, > 0)\n- lookback_days: Historical data period (7-120). Default: 30\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- target_roas: Target ROAS for optimization\n- max_change_percentage: Max budget change (0.3-0.7). Default: 0.5\n- min_daily_budget: Minimum daily budget. Default: $20\n- optimization_goal: 'conversions' or 'leads'. Default: conversions\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExample Prompts:\n- \"How should I allocate $30K across LinkedIn campaigns?\"\n- \"Optimize my LinkedIn budget for leads\"\n- \"Which LinkedIn campaigns should get more budget?\"\n- \"How can I improve LinkedIn campaign efficiency?\"\n\nExecution time: 4-6 seconds",
        "operationId": "execute_optimize_linkedin_budget",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string",
                  "target_roas": 0.1,
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for LinkedIn budget optimization",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days of historical data to use for optimization. Default: 30.",
                        "maximum": 120,
                        "minimum": 7,
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "max_change_percentage": {
                        "default": 0.5,
                        "description": "Maximum budget change per campaign (0.3-0.7). Default: 0.5 (50%)",
                        "maximum": 0.7,
                        "minimum": 0.3,
                        "title": "Max Change Percentage",
                        "type": "number"
                      },
                      "min_daily_budget": {
                        "default": 20.0,
                        "description": "Minimum daily budget per campaign. LinkedIn minimum is $10. Default: $20",
                        "minimum": 10.0,
                        "title": "Min Daily Budget",
                        "type": "number"
                      },
                      "optimization_goal": {
                        "default": "conversions",
                        "description": "Optimization goal: 'conversions' or 'leads'. Default: conversions",
                        "title": "Optimization Goal",
                        "type": "string"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "minimum": 0.1,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target ROAS for optimization. If not provided, uses historical average.",
                        "title": "Target Roas"
                      },
                      "total_budget": {
                        "description": "Total monthly budget to allocate across campaigns (required, > 0)",
                        "exclusiveMinimum": 0,
                        "title": "Total Budget",
                        "type": "number"
                      }
                    },
                    "required": [
                      "total_budget"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for optimize_linkedin_budget)"
                  },
                  "success": true,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: optimize_linkedin_budget",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_linkedin_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks how to allocate their LinkedIn budget,\nwants budget optimization recommendations, or asks \"How should I spl...",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "optimize_linkedin_budget"
      }
    },
    "/api/v1/tools/optimize_meta_budget/execute": {
      "post": {
        "description": "User asks about Meta/Facebook/Instagram budget optimization, reallocating ad spend, maximizing conversions with their budget, or wants data-driven budget recommendations.\n\nThis 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.\n\nReturns:\n- Optimal budget allocation for each campaign/ad set\n- Expected conversion lift from reallocation\n- Campaigns to scale up (high ROAS performers)\n- Campaigns to reduce (below target ROAS)\n- Campaigns to consider pausing (ROAS < 1.0)\n- Actionable recommendations\n- CBO (Campaign Budget Optimization) notes\n\nWhen to use this tool:\n- \"How should I allocate my Meta budget?\"\n- \"Optimize my Facebook ad spend for conversions\"\n- \"Which Instagram campaigns should I increase budget?\"\n- \"Reallocate my $5000 Meta budget\"\n- \"Maximize conversions with my current spend\"\n- \"What's the optimal budget split across campaigns?\"\n\nParameters:\n- total_budget: Total daily budget to allocate (required, e.g., 5000.00)\n- lookback_days: 7, 14, 30 (default), 60, or 90 days for analysis\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- target_roas: Optional override (default: from account goals or 2.0x)\n- max_change_percentage: Max budget change per item (default: 0.5 = 50%)\n- min_daily_budget: Minimum budget per item (default: 5.00)\n- optimization_level: 'campaign' (default) or 'ad_set'\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 2-5 seconds (cached database query + optimization)\nData source: campaign_daily_metrics + ad_group_daily_metrics tables\n\nKey concepts:\n- Linear Programming: Mathematical optimization to maximize objective (conversions) subject to constraints\n- Efficiency Score: Conversions per dollar spent - used to prioritize allocation\n- Max Change Constraint: Prevents dramatic shifts (e.g., \u00b150% max from current)\n- Target ROAS: 3-tier resolution: account_goals - 90-day historical - default 2.0x\n\nMeta-specific considerations:\n- CBO campaigns: Budget set at campaign level, Meta distributes to ad sets\n- ABO campaigns: Budget set at ad set level, more granular control\n- Learning Phase: New campaigns need 50+ conversions before optimization",
        "operationId": "execute_optimize_meta_budget",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "raw_data": false,
                  "start_date": "string",
                  "target_roas": 1.0,
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta budget allocation optimization",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze for historical performance (7, 30, 60, 90, or 120 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "max_change_percentage": {
                        "default": 0.5,
                        "description": "Maximum allowed budget change per campaign as decimal (0.3 for \u00b130%, 0.5 for \u00b150%, 0.7 for \u00b170%). Default is 0.5.",
                        "title": "Max Change Percentage",
                        "type": "number"
                      },
                      "min_daily_budget": {
                        "default": 5.0,
                        "description": "Minimum daily budget per campaign in dollars. Default is $5.00.",
                        "title": "Min Daily Budget",
                        "type": "number"
                      },
                      "optimization_level": {
                        "default": "campaign",
                        "description": "Level of optimization: 'campaign' (default) or 'ad_set'. Note: Ad set optimization may not apply to CBO-enabled campaigns.",
                        "title": "Optimization Level",
                        "type": "string"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional target ROAS override (e.g., 2.0 for 2.0x ROAS). If not provided, will use account goals or historical average.",
                        "title": "Target Roas"
                      },
                      "total_budget": {
                        "description": "Total monthly budget to allocate across campaigns (in dollars). Required.",
                        "title": "Total Budget",
                        "type": "number"
                      }
                    },
                    "required": [
                      "total_budget"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for optimize_meta_budget)"
                  },
                  "success": true,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: optimize_meta_budget",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about Meta/Facebook/Instagram budget optimization, reallocating ad spend, maximizing conversions with their...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "optimize_meta_budget"
      }
    },
    "/api/v1/tools/optimize_meta_placements/execute": {
      "post": {
        "description": "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.\n\nThis tool analyzes placement-level performance (Feed, Stories, Reels, Audience Network, Messenger, etc.) and provides optimization recommendations including budget reallocation suggestions.\n\nReturns:\n- Placement ROAS ranking (sorted by performance)\n- Placements categorized as SCALE/MAINTAIN/REDUCE/EXCLUDE\n- Budget reallocation recommendations between placements\n- Expected ROAS improvement from optimization\n- Optimal placement mix for campaign objective\n- Savings from excluding underperforming placements\n- Actionable recommendations and quick actions\n\nWhen to use this tool:\n- \"Which Meta placements should I use?\"\n- \"Should I exclude Audience Network?\"\n- \"Instagram Stories vs Reels - which is better?\"\n- \"Feed vs Stories performance comparison\"\n- \"Where should I allocate my Meta ad budget?\"\n- \"Which placements are wasting money?\"\n- \"What's the optimal placement mix for conversions?\"\n- \"Facebook Marketplace performance?\"\n\nParameters:\n- lookback_days: 7, 14, 30 (default), 60, or 90 days\n- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.\n- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.\n\u26a0\ufe0f 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.\n- objective: 'conversions' (default), 'traffic', or 'awareness' - for optimal mix recommendations\n- target_roas: Optional override (default: from account goals or 2.0x)\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 2-5 seconds (cached database query with analysis)\nData source: meta_placement_daily_metrics table (placement-level daily metrics)\n\nROAS Thresholds for Recommendations:\n- \u2705 SCALE (ROAS \u22653.0x): Increase budget by 20-30%\n- \u2796 MAINTAIN (ROAS 1.5-3.0x): Keep current budget\n- \u26a0\ufe0f REDUCE (ROAS 1.0-1.5x): Reduce budget by 30-50%\n- \ud83d\udd34 EXCLUDE (ROAS <1.0x): Remove from campaigns\n\nMeta Placements Analyzed:\n- Facebook: Feed, Stories, Reels, Marketplace, Search Results\n- Instagram: Feed, Stories, Reels, Explore\n- Messenger: Inbox, Stories\n- Audience Network: All placements\n\nCommon Insights:\n- Audience Network often has lowest ROAS for conversion campaigns - consider excluding\n- Instagram Feed and Reels typically have highest conversion rates\n- Stories are great for awareness but may have lower conversion rates\n- Facebook Marketplace can be effective for e-commerce",
        "operationId": "execute_optimize_meta_placements",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "date_range": "string",
                  "end_date": "string",
                  "lookback_days": 30,
                  "objective": "conversions",
                  "raw_data": false,
                  "start_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta placement optimization analysis",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "date_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Date Range"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries.",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days.",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "objective": {
                        "default": "conversions",
                        "description": "Campaign objective for optimal placement mix recommendations: 'conversions' (default), 'traffic', or 'awareness'.",
                        "title": "Objective",
                        "type": "string"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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 recommendations. Use when you run your own attribution model or want to minimize token usage.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries.",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average.",
                        "title": "Target Roas"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for optimize_meta_placements)"
                  },
                  "success": true,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: optimize_meta_placements",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_meta_placements"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User asks about Meta/Facebook/Instagram placement performance, which placements work best, Feed vs Stories vs Reels, ...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "optimize_meta_placements"
      }
    },
    "/api/v1/tools/optimize_tiktok_budget/execute": {
      "post": {
        "description": "Optimize TikTok budget allocation using linear programming to maximize conversions.\n\nReturns: current vs optimized allocation, expected conversion lift, campaigns to scale/reduce/pause/maintain.\n\nWhen to use: \"Optimize my TikTok budget\", \"How should I allocate TikTok spend?\", \"TikTok budget recommendations\"\n\nParameters:\n- total_budget: REQUIRED \u2014 total monthly budget to allocate\n- target_roas: optional override (default uses historical avg or 2.0x)\n- max_change_percentage: 0.3 (conservative) to 0.7 (aggressive), default 0.5",
        "operationId": "execute_optimize_tiktok_budget",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "lookback_days": 30,
                  "max_change_percentage": 0.5,
                  "min_daily_budget": 5.0,
                  "start_date": "string",
                  "target_roas": 1.0,
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End date YYYY-MM-DD",
                        "title": "End Date"
                      },
                      "lookback_days": {
                        "default": 30,
                        "description": "Days to analyze for historical performance",
                        "title": "Lookback Days",
                        "type": "integer"
                      },
                      "max_change_percentage": {
                        "default": 0.5,
                        "description": "Max budget change per campaign (0.3=conservative, 0.7=aggressive)",
                        "title": "Max Change Percentage",
                        "type": "number"
                      },
                      "min_daily_budget": {
                        "default": 5.0,
                        "description": "Minimum daily budget per campaign",
                        "title": "Min Daily Budget",
                        "type": "number"
                      },
                      "raw_data": {
                        "default": false,
                        "description": "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.",
                        "title": "Raw Data",
                        "type": "boolean"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Start date YYYY-MM-DD",
                        "title": "Start Date"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target ROAS override",
                        "title": "Target Roas"
                      },
                      "total_budget": {
                        "description": "Total monthly budget to allocate",
                        "title": "Total Budget",
                        "type": "number"
                      }
                    },
                    "required": [
                      "total_budget"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for optimize_tiktok_budget)"
                  },
                  "success": true,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: optimize_tiktok_budget",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "optimize_tiktok_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Optimize TikTok budget allocation using linear programming to maximize conversions",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "optimize_tiktok_budget"
      }
    },
    "/api/v1/tools/pause_ad/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nPause an ad to stop it from showing.\n\nThis is REVERSIBLE using resume_ad.\n\n**Parameters:**\n- ad_id: The ad ID to pause (REQUIRED)\n- ad_group_id: The ad group ID (REQUIRED)\n- customer_id: Optional\n\n**Execution time:** 1-2 seconds\n\n**When to use:**\n- User says \"pause this ad\", \"stop this ad\"\n- User wants to A/B test by pausing one ad\n- Ad is underperforming and needs a break\n- Making changes before re-enabling\n\n**Example:**\nUser: \"Pause my underperforming ad\"\nAgent:\n1. Uses get_campaign_structure to find ad_id and ad_group_id\n2. Uses pause_ad to stop the ad",
        "operationId": "execute_pause_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "ad_id": "string",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for pausing an ad",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the ad belongs to. Get from get_campaign_structure.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "ad_id": {
                        "description": "The ad ID to pause. Get from get_campaign_structure.",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "ad_id",
                      "ad_group_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_ad)"
                  },
                  "success": true,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_ad"
      }
    },
    "/api/v1/tools/pause_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nQuickly pause a running campaign.\n\nPausing a campaign stops all ads from showing immediately.\nThis is REVERSIBLE using resume_campaign.\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 1-2 seconds\n\n**Returns:**\n- Campaign name\n- Before status (e.g., ENABLED)\n- After status (PAUSED)\n- Confirmation message\n\n**When to use:**\n- User says \"pause the campaign\", \"stop the ads\", \"turn it off\"\n- User wants to temporarily stop spending\n- User needs to make changes before ads continue\n\n**Example:**\nUser: \"Pause my summer sale campaign\"\nAgent: Uses pause_campaign with the campaign_id",
        "operationId": "execute_pause_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for pausing a campaign",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to pause. Use list_campaigns first to get available campaign IDs.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_campaign)"
                  },
                  "success": true,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_campaign"
      }
    },
    "/api/v1/tools/pause_linkedin_campaign/execute": {
      "post": {
        "description": "User wants to pause an active LinkedIn campaign.\n\nPause a campaign to stop serving impressions.\n\nParameters:\n- campaign_id: Campaign ID to pause (required)\n- account_id: Optional LinkedIn Ad Account ID\n\nWhat happens:\n- Campaign status changes to PAUSED\n- No new impressions will be served\n- Campaign can be resumed later\n\nExecution time: 2-3 seconds",
        "operationId": "execute_pause_linkedin_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for pausing a LinkedIn campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to pause",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_linkedin_campaign)"
                  },
                  "success": true,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_linkedin_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to pause an active LinkedIn campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_linkedin_campaign"
      }
    },
    "/api/v1/tools/pause_linkedin_creative/execute": {
      "post": {
        "description": "User wants to pause a specific ad within a campaign.\n\nPause an individual creative/ad without affecting other ads in the campaign.\n\nParameters:\n- creative_id: Creative ID to pause (required)\n- account_id: Optional LinkedIn Ad Account ID\n\nUse Case:\n- Pause underperforming ads\n- A/B test by pausing certain variations\n- Temporarily disable specific content\n\nExecution time: 2-3 seconds",
        "operationId": "execute_pause_linkedin_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "creative_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for pausing a LinkedIn creative",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "creative_id": {
                        "description": "Creative ID to pause",
                        "title": "Creative Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "creative_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_linkedin_creative)"
                  },
                  "success": true,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_linkedin_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to pause a specific ad within a campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_linkedin_creative"
      }
    },
    "/api/v1/tools/pause_meta_campaign/execute": {
      "post": {
        "description": "User wants to pause a running Meta campaign.\n\nIMPORTANT: Pausing a campaign stops all ad delivery immediately. No more budget will be spent until resumed.\n\nReturns:\n- Confirmation that campaign is paused\n- Campaign details\n- Ads Manager URL\n\nWhen to use this tool:\n- \"Pause my Meta campaign\"\n- \"Stop my Facebook ads\"\n- \"Pause campaign 123456\"\n- \"Turn off my Instagram campaign\"\n\nParameters:\n- campaign_id: The Meta Campaign ID to pause (required)\n\nExecution time: 2-5 seconds\nEffect: Campaign status changes to PAUSED immediately",
        "operationId": "execute_pause_meta_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for pausing a Meta campaign",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "description": "The Meta Campaign ID to pause (required)",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_meta_campaign)"
                  },
                  "success": true,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_meta_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to pause a running Meta campaign [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_meta_campaign"
      }
    },
    "/api/v1/tools/pause_tiktok_ad/execute": {
      "post": {
        "description": "Pause a TikTok ad. Sets status to DISABLE.",
        "operationId": "execute_pause_tiktok_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_id": "string",
                  "advertiser_id": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for pausing/resuming/deleting a TikTok ad",
                    "properties": {
                      "ad_id": {
                        "description": "TikTok ad ID",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "status": {
                        "description": "New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "ad_id",
                      "status"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_tiktok_ad)"
                  },
                  "success": true,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_tiktok_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Pause a TikTok ad [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_tiktok_ad"
      }
    },
    "/api/v1/tools/pause_tiktok_ad_group/execute": {
      "post": {
        "description": "Pause a TikTok ad group. Sets status to DISABLE.",
        "operationId": "execute_pause_tiktok_ad_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "adgroup_id": "string",
                  "advertiser_id": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for pausing/resuming/deleting a TikTok ad group",
                    "properties": {
                      "adgroup_id": {
                        "description": "TikTok ad group ID",
                        "title": "Adgroup Id",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "status": {
                        "description": "New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "adgroup_id",
                      "status"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_tiktok_ad_group)"
                  },
                  "success": true,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_tiktok_ad_group",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Pause a TikTok ad group [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_tiktok_ad_group"
      }
    },
    "/api/v1/tools/pause_tiktok_campaign/execute": {
      "post": {
        "description": "Pause a TikTok campaign. Sets status to DISABLE. Use resume_tiktok_campaign to re-enable.",
        "operationId": "execute_pause_tiktok_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "campaign_id": "<campaign_id>",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for pausing/resuming/deleting a TikTok campaign",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "campaign_id": {
                        "description": "TikTok campaign ID",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "status": {
                        "description": "New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "status"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for pause_tiktok_campaign)"
                  },
                  "success": true,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: pause_tiktok_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "pause_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Pause a TikTok campaign [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "pause_tiktok_campaign"
      }
    },
    "/api/v1/tools/remove_keywords/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nRemove keywords from an ad group.\n\n**Parameters:**\n- ad_group_id: The ad group containing the keywords (REQUIRED)\n- keyword_ids: List of keyword IDs to remove (REQUIRED)\n- customer_id: Optional\n\n**Get keyword_ids from:**\nUse get_campaign_structure to see all keywords with their IDs.\n\n**WARNING: This is PERMANENT and IRREVERSIBLE.**\nKeywords removed cannot be recovered. Always confirm with the user first.\n\n**Execution time:** 2-4 seconds\n\n**Example:**\nUser: \"Remove the underperforming keywords\"\nAgent:\n1. Uses get_campaign_structure to show keywords\n2. Confirms with user which ones to remove\n3. Uses remove_keywords with the keyword_ids",
        "operationId": "execute_remove_keywords",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "customer_id": "string",
                  "keyword_ids": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for removing keywords",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the keywords belong to. Use get_campaign_structure to find this.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "keyword_ids": {
                        "description": "List of keyword IDs to remove. Get from get_campaign_structure.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Keyword Ids",
                        "type": "array"
                      }
                    },
                    "required": [
                      "ad_group_id",
                      "keyword_ids"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for remove_keywords)"
                  },
                  "success": true,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: remove_keywords",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "remove_keywords"
      }
    },
    "/api/v1/tools/remove_negative_keywords/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nRemove negative keywords from a campaign.\n\n\u26a0\ufe0f **WARNING: This is PERMANENT and IRREVERSIBLE.**\nOnce removed, the negative keywords cannot be recovered.\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED)\n- keyword_ids: List of negative keyword IDs to remove (REQUIRED)\n- customer_id: Optional\n\n**Get keyword IDs from:**\nUse `get_campaign_structure` to see all campaign negative keywords with their IDs.\nLook for the \"Campaign Negative Keywords\" section.\n\n**When to use:**\n- Remove accidentally added negative keywords\n- Clean up obsolete negative keywords\n- Fix over-blocking that's limiting traffic\n\n**Execution time:** 2-4 seconds\n\n**Example:**\nUser: \"Remove the negative keyword 'discount' I added by mistake\"\nAgent:\n1. Uses get_campaign_structure to find the keyword ID\n2. Uses remove_negative_keywords with campaign_id and keyword_ids",
        "operationId": "execute_remove_negative_keywords",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "keyword_ids": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for removing negative keywords from a campaign",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to remove negative keywords from.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "keyword_ids": {
                        "description": "List of negative keyword IDs to remove. Get IDs from get_campaign_structure (Campaign Negative Keywords section).",
                        "items": {
                          "type": "string"
                        },
                        "title": "Keyword Ids",
                        "type": "array"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "keyword_ids"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for remove_negative_keywords)"
                  },
                  "success": true,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: remove_negative_keywords",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_negative_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "remove_negative_keywords"
      }
    },
    "/api/v1/tools/remove_pmax_audience_signal/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nRemove a specific audience signal from a Performance Max campaign.\n\nUse get_pmax_audience_signals first to get the signal_resource_name.\n\n**Parameters:**\n- campaign_id: The PMax campaign ID\n- signal_resource_name: Resource name from get_pmax_audience_signals\n\n**Execution time:** 2-5 seconds",
        "operationId": "execute_remove_pmax_audience_signal",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "signal_resource_name": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for removing an audience signal from a PMax campaign.",
                    "properties": {
                      "campaign_id": {
                        "description": "The Google Ads campaign ID (numeric). Example: '21854471508'",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "signal_resource_name": {
                        "description": "The resource name of the audience signal to remove. Get from get_pmax_audience_signals tool. Example: 'customers/1234567890/assetGroupSignals/123~456'",
                        "title": "Signal Resource Name",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "signal_resource_name"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for remove_pmax_audience_signal)"
                  },
                  "success": true,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: remove_pmax_audience_signal",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_audience_signal"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "remove_pmax_audience_signal"
      }
    },
    "/api/v1/tools/remove_pmax_search_themes/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nRemove specific search themes from a Performance Max campaign.\n\nMatches themes by text (case-insensitive). Use get_pmax_search_themes first to see current themes.\n\n**Parameters:**\n- campaign_id: The PMax campaign ID\n- themes_to_remove: List of theme text strings to remove\n\n**Execution time:** 2-5 seconds",
        "operationId": "execute_remove_pmax_search_themes",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "themes_to_remove": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for removing search themes from a PMax campaign.",
                    "properties": {
                      "campaign_id": {
                        "description": "The Google Ads campaign ID (numeric). Example: '21854471508'",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "themes_to_remove": {
                        "description": "List of search theme texts to remove (case-insensitive match). Example: ['AI ad management', 'automate google ads']",
                        "items": {
                          "type": "string"
                        },
                        "minItems": 1,
                        "title": "Themes To Remove",
                        "type": "array"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "themes_to_remove"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for remove_pmax_search_themes)"
                  },
                  "success": true,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: remove_pmax_search_themes",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "remove_pmax_search_themes"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": true,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "remove_pmax_search_themes"
      }
    },
    "/api/v1/tools/research_business_for_linkedin_targeting/execute": {
      "post": {
        "description": "User wants targeting recommendations based on their business.\n\nAnalyze a business to derive appropriate LinkedIn targeting recommendations.\n\nWhat this tool does:\n- Analyzes business website URL and description\n- Identifies likely target audience based on business vertical\n- Recommends seniority levels, job functions, industries, company sizes\n- Suggests job titles and interests to search for\n\nParameters:\n- website_url: Business website URL (required)\n- business_description: What the business does (optional)\n- ideal_customer_description: Who they want to reach (optional)\n- competitors: List of competitor names/websites (optional)\n\nReturns:\n- Recommended seniority URNs (copy directly)\n- Recommended company size URNs (copy directly)\n- Industry recommendations\n- Job function recommendations\n- Job title suggestions (use with search_linkedin_targeting)\n- Interest suggestions\n\nExample Prompts:\n- \"Suggest LinkedIn targeting for my marketing SaaS\"\n- \"Who should I target for my HR software on LinkedIn?\"\n- \"Help me set up LinkedIn targeting for my B2B product\"\n\nExecution time: 1-2 seconds",
        "operationId": "execute_research_business_for_linkedin_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "business_description": "string",
                  "competitors": [
                    "string"
                  ],
                  "ideal_customer_description": "string",
                  "website_url": "https://example.com"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for researching business for LinkedIn targeting",
                    "properties": {
                      "business_description": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional description of what the business does",
                        "title": "Business Description"
                      },
                      "competitors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional list of competitor names or websites",
                        "title": "Competitors"
                      },
                      "ideal_customer_description": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional description of who the business wants to reach",
                        "title": "Ideal Customer Description"
                      },
                      "website_url": {
                        "description": "Customer's website URL to analyze for targeting recommendations",
                        "title": "Website Url",
                        "type": "string"
                      }
                    },
                    "required": [
                      "website_url"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for research_business_for_linkedin_targeting)"
                  },
                  "success": true,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: research_business_for_linkedin_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "research_business_for_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants targeting recommendations based on their business",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "research_business_for_linkedin_targeting"
      }
    },
    "/api/v1/tools/research_keywords/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nResearch high-intent keywords using Google Keyword Planner API.\n\n\u26a0\ufe0f IMPORTANT: This is a READ-ONLY tool. Safe to call multiple times.\n\n\ud83c\udfaf **What This Tool Does:**\n- Researches keywords via Google Keyword Planner API\n- Returns keywords with real CPC data, search volume, and competition metrics\n- Groups keywords by commercial intent (HIGH/MEDIUM/LOW based on dynamic CPC thresholds)\n- Selects top 15-20 keywords optimized for conversions\n- Provides budget recommendations based on actual keyword costs\n\n**When to Use:**\n- BEFORE creating a Google Search campaign\n- When you need data-driven keyword insights\n- To understand keyword costs and search volume\n- To get budget recommendations\n\n**Parameters:**\n- business_description (required): What the business sells/offers\n- website_url (optional): Business website for better keyword suggestions\n- target_location (optional): Geographic target (default: \"United States\")\n- seed_keywords (optional): 5-10 seed keywords (will auto-extract if not provided)\n- customer_id (optional): Google Ads account ID\n\n**Returns:**\n- Keyword table with dynamic CPC thresholds (adapts to any industry)\n- HIGH/MEDIUM/LOW intent grouping\n- Budget recommendations (Conservative/Moderate/Aggressive)\n- Top 15-20 recommended keywords for campaign\n\n**Execution time:** 3-8 seconds (calls live Google Ads API)\n\n\ud83d\udcca **Example Usage:**\n1. User: \"I want to create a campaign for my plumbing business\"\n2. YOU call: research_keywords with business_description=\"Emergency plumbing services\"\n3. Tool returns: Keyword table with 100+ keywords, 20 recommended, budget suggestions\n4. YOU show user: The keyword table and ask if they want modifications\n5. User approves or requests changes\n6. YOU call: create_search_campaign with approved keywords\n\n\ud83d\udca1 **Dynamic Thresholds:** This tool automatically adapts CPC thresholds to any industry:\n- Plumbing: HIGH \u2265$6, MEDIUM $3-6, LOW <$3\n- Legal: HIGH \u2265$95, MEDIUM $45-95, LOW <$45\n- E-commerce: HIGH \u2265$2, MEDIUM $0.50-2, LOW <$0.50\n\nAll keywords returned will use **BROAD match** (Google's 2025 recommendation with Smart Bidding).\n\n---\n\n\ud83d\udcca **CRITICAL: AFTER calling this tool, YOU MUST explain these insights to the user:**\n\n**1. Keyword Discovery Summary:**\n   - \"I found [X] keywords from Google Keyword Planner for your [business type] business\"\n   - \"I analyzed real search data and CPC costs from Google Ads\"\n   - \"Here are the top 20 keywords I recommend based on commercial intent\"\n\n**2. Seed Keywords Used:**\n   - \"I used these seed keywords: [list the seeds from the response]\"\n   - \"Google expanded these into [X] keyword suggestions\"\n\n**3. CPC Cost Analysis (CRITICAL - Discuss this with user!):**\n   - \"The median CPC for your industry is $[X]\"\n   - \"Keywords range from $[LOW] to $[HIGH] per click\"\n   - \"HIGH intent keywords (top 25% most expensive) cost $[threshold]+ per click\"\n   - \"These are keywords where advertisers pay more = higher commercial value\"\n\n**4. Budget Recommendations (CRITICAL - Explain all 3 tiers!):**\n   - \"Based on the keyword costs, here are my budget recommendations:\"\n   - \"\ud83d\udcb0 Conservative ($[X]/day): Safe starting budget based on median CPC, expect ~[Y] clicks/day\"\n   - \"\ud83d\udcb0 Moderate ($[X]/day): Balanced budget based on average CPC, expect ~[Y] clicks/day\"\n   - \"\ud83d\udcb0 Aggressive ($[X]/day): Maximum budget to compete for all keywords, expect ~[Y] clicks/day\"\n   - \"I recommend starting with $[conservative]-[moderate]/day\"\n\n**5. Keyword Selection Explanation:**\n   - \"I selected these 20 keywords by prioritizing:\"\n   - \"  \u2022 HIGH intent keywords (expensive = high commercial value)\"\n   - \"  \u2022 High search volume (more potential customers)\"\n   - \"  \u2022 Mix of broad and specific terms\"\n   - \"All keywords will use BROAD match - Google's 2025 recommendation for maximum reach with Smart Bidding\"\n\n**6. Ask for User Feedback:**\n   - \"Would you like to modify this keyword selection?\"\n   - \"Options: Add specific keywords, remove keywords, use only HIGH intent, or proceed with recommendations\"\n\n**DO NOT just show the raw table without explanation!**\n**Users need YOU to interpret the data and provide strategic guidance!**",
        "operationId": "execute_research_keywords",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "business_description": "string",
                  "customer_id": "string",
                  "seed_keywords": [
                    "string"
                  ],
                  "target_location": "United States",
                  "website_url": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for keyword research using Google Keyword Planner",
                    "properties": {
                      "business_description": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Description of what the business sells/does. Required if seed_keywords not provided. If omitted, inferred from website_url domain.",
                        "title": "Business Description"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "seed_keywords": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Provide 5-10 seed keywords. If not provided, they will be extracted from business_description automatically.",
                        "title": "Seed Keywords"
                      },
                      "target_location": {
                        "default": "United States",
                        "description": "Geographic target for keyword research (e.g., 'New York, NY', 'Chicago, IL', 'United States')",
                        "title": "Target Location",
                        "type": "string"
                      },
                      "website_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Business website URL (helps generate more relevant keywords)",
                        "title": "Website Url"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for research_keywords)"
                  },
                  "success": true,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: research_keywords",
                  "is_error": true,
                  "success": false,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "research_keywords"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "research_keywords"
      }
    },
    "/api/v1/tools/resume_ad/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nResume a paused ad to start showing it again.\n\nOnly works on PAUSED ads (not REMOVED).\n\n**Parameters:**\n- ad_id: The ad ID to resume (REQUIRED)\n- ad_group_id: The ad group ID (REQUIRED)\n- customer_id: Optional\n\n**Execution time:** 1-2 seconds\n\n**When to use:**\n- User says \"resume\", \"turn it back on\", \"enable\"\n- User finished making changes\n- User wants to reactivate a paused A/B test variant\n\n**Example:**\nUser: \"Turn my paused ad back on\"\nAgent:\n1. Uses get_campaign_structure to find the paused ad\n2. Uses resume_ad to enable it",
        "operationId": "execute_resume_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "ad_id": "string",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for resuming a paused ad",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the ad belongs to. Get from get_campaign_structure.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "ad_id": {
                        "description": "The ad ID to resume. Get from get_campaign_structure.",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "ad_id",
                      "ad_group_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_ad)"
                  },
                  "success": true,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_ad"
      }
    },
    "/api/v1/tools/resume_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nResume a paused campaign.\n\nResuming a campaign makes ads start showing again.\nOnly works on PAUSED campaigns (not REMOVED).\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 1-2 seconds\n\n**Returns:**\n- Campaign name\n- Before status (e.g., PAUSED)\n- After status (ENABLED)\n- Confirmation message\n\n**When to use:**\n- User says \"resume\", \"turn it back on\", \"start it again\"\n- User wants to re-enable a paused campaign\n- User finished making changes and wants ads to run\n\n**Example:**\nUser: \"Turn my campaign back on\"\nAgent: Uses resume_campaign with the campaign_id",
        "operationId": "execute_resume_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for resuming a paused campaign",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to resume. Use list_campaigns first to get available campaign IDs.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_campaign)"
                  },
                  "success": true,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_campaign"
      }
    },
    "/api/v1/tools/resume_linkedin_campaign/execute": {
      "post": {
        "description": "User wants to resume a paused LinkedIn campaign.\n\nResume a paused campaign to start serving impressions again.\n\nParameters:\n- campaign_id: Campaign ID to resume (required)\n- account_id: Optional LinkedIn Ad Account ID\n\nWhat happens:\n- Campaign status changes to ACTIVE\n- Impressions will start serving\n- Learning phase resumes\n\nExecution time: 2-3 seconds",
        "operationId": "execute_resume_linkedin_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for resuming a paused LinkedIn campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to resume",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_linkedin_campaign)"
                  },
                  "success": true,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_linkedin_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to resume a paused LinkedIn campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_linkedin_campaign"
      }
    },
    "/api/v1/tools/resume_linkedin_creative/execute": {
      "post": {
        "description": "User wants to resume a paused ad.\n\nResume a paused creative/ad to include it in campaign rotation.\n\nParameters:\n- creative_id: Creative ID to resume (required)\n- account_id: Optional LinkedIn Ad Account ID\n\nExecution time: 2-3 seconds",
        "operationId": "execute_resume_linkedin_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "creative_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for resuming a LinkedIn creative",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "creative_id": {
                        "description": "Creative ID to resume",
                        "title": "Creative Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "creative_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_linkedin_creative)"
                  },
                  "success": true,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_linkedin_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to resume a paused ad [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_linkedin_creative"
      }
    },
    "/api/v1/tools/resume_meta_campaign/execute": {
      "post": {
        "description": "User wants to resume a paused Meta campaign.\n\nIMPORTANT: Resuming a campaign restarts ad delivery. Budget will start being spent again.\n\nReturns:\n- Confirmation that campaign is active\n- Campaign details\n- Ads Manager URL\n\nWhen to use this tool:\n- \"Resume my Meta campaign\"\n- \"Turn on my Facebook ads\"\n- \"Reactivate campaign 123456\"\n- \"Start my paused Instagram campaign\"\n\nParameters:\n- campaign_id: The Meta Campaign ID to resume (required)\n\nExecution time: 2-5 seconds\nEffect: Campaign status changes to ACTIVE immediately",
        "operationId": "execute_resume_meta_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "campaign_id": "<campaign_id>"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for resuming a Meta campaign",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "description": "The Meta Campaign ID to resume (required)",
                        "title": "Campaign Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_meta_campaign)"
                  },
                  "success": true,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_meta_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to resume a paused Meta campaign [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_meta_campaign"
      }
    },
    "/api/v1/tools/resume_tiktok_ad/execute": {
      "post": {
        "description": "Resume a paused TikTok ad. Sets status to ENABLE.",
        "operationId": "execute_resume_tiktok_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_id": "string",
                  "advertiser_id": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for pausing/resuming/deleting a TikTok ad",
                    "properties": {
                      "ad_id": {
                        "description": "TikTok ad ID",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "status": {
                        "description": "New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "ad_id",
                      "status"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_tiktok_ad)"
                  },
                  "success": true,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_tiktok_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Resume a paused TikTok ad [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_tiktok_ad"
      }
    },
    "/api/v1/tools/resume_tiktok_ad_group/execute": {
      "post": {
        "description": "Resume a paused TikTok ad group. Sets status to ENABLE.",
        "operationId": "execute_resume_tiktok_ad_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "adgroup_id": "string",
                  "advertiser_id": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for pausing/resuming/deleting a TikTok ad group",
                    "properties": {
                      "adgroup_id": {
                        "description": "TikTok ad group ID",
                        "title": "Adgroup Id",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "status": {
                        "description": "New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "adgroup_id",
                      "status"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_tiktok_ad_group)"
                  },
                  "success": true,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_tiktok_ad_group",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Resume a paused TikTok ad group [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_tiktok_ad_group"
      }
    },
    "/api/v1/tools/resume_tiktok_campaign/execute": {
      "post": {
        "description": "Resume a paused TikTok campaign. Sets status to ENABLE.",
        "operationId": "execute_resume_tiktok_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "campaign_id": "<campaign_id>",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for pausing/resuming/deleting a TikTok campaign",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "campaign_id": {
                        "description": "TikTok campaign ID",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "status": {
                        "description": "New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE'",
                        "title": "Status",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "status"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for resume_tiktok_campaign)"
                  },
                  "success": true,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: resume_tiktok_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "resume_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Resume a paused TikTok campaign [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "resume_tiktok_campaign"
      }
    },
    "/api/v1/tools/save_business_profile/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nSave or update the user's business profile with provided details.\n\n\u26a0\ufe0f IMPORTANT: This tool WRITES data. Use when user confirms their business profile.\n\n\ud83c\udfaf **What This Tool Does (Performance Agent - Phase 1 Feature 5):**\n- Saves user's business profile to database\n- Updates existing profile if one exists\n- Sets profile source as 'user_confirmed' or 'user_mcp'\n- Enables personalized recommendations going forward\n\n**Required Parameters:**\n- **business_vertical**: retail, services, technology, healthcare, finance, education, travel, food_beverage, automotive, real_estate\n- **business_size**: small (<$50K/month), medium ($50K-$500K/month), large (>$500K/month)\n- **primary_goal**: leads, sales, awareness, traffic, engagement, app_installs\n\n**Optional Parameters:**\n- **target_audience**: Free-text description (e.g., \"B2B enterprise clients\")\n- **geographic_focus**: local, regional, national, international\n- **seasonality**: none, holiday_heavy, summer_peak, winter_peak, q4_heavy, back_to_school\n- **customer_id**: Optional (uses connected account if omitted)\n\n**Use this tool when:**\n- User provides their business details\n- User confirms an inferred profile\n- User wants to update their business profile\n- After asking user clarifying questions about their business\n\n**Example Usage:**\nUser: \"I run a small local plumbing business targeting homeowners\"\n\u2192 Call save_business_profile with:\n  - business_vertical: \"services\"\n  - business_size: \"small\"\n  - primary_goal: \"leads\"\n  - target_audience: \"homeowners\"\n  - geographic_focus: \"local\"\n\n**Execution time:** 1-2 seconds (database write)",
        "operationId": "execute_save_business_profile",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "business_size": "string",
                  "business_vertical": "string",
                  "customer_id": "string",
                  "geographic_focus": "string",
                  "primary_goal": "string",
                  "seasonality": "string",
                  "target_audience": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for saving/updating business profile",
                    "properties": {
                      "business_size": {
                        "description": "Business size: 'small' (<$50K/month ad spend), 'medium' ($50K-$500K/month), 'large' (>$500K/month)",
                        "title": "Business Size",
                        "type": "string"
                      },
                      "business_vertical": {
                        "description": "Business vertical (e.g., 'retail', 'services', 'technology', 'healthcare', 'finance', 'education', 'travel', 'food_beverage', 'automotive', 'real_estate')",
                        "title": "Business Vertical",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "geographic_focus": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Geographic focus: 'local', 'regional', 'national', 'international'",
                        "title": "Geographic Focus"
                      },
                      "primary_goal": {
                        "description": "Primary advertising goal: 'leads', 'sales', 'awareness', 'traffic', 'engagement', 'app_installs'",
                        "title": "Primary Goal",
                        "type": "string"
                      },
                      "seasonality": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Seasonality pattern: 'none', 'holiday_heavy', 'summer_peak', 'winter_peak', 'q4_heavy', 'back_to_school'",
                        "title": "Seasonality"
                      },
                      "target_audience": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target audience description (e.g., 'B2B enterprise clients', 'young professionals 25-35')",
                        "title": "Target Audience"
                      }
                    },
                    "required": [
                      "business_vertical",
                      "business_size",
                      "primary_goal"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for save_business_profile)"
                  },
                  "success": true,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: save_business_profile",
                  "is_error": true,
                  "success": false,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "save_business_profile"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "save_business_profile"
      }
    },
    "/api/v1/tools/schedule_brief/execute": {
      "post": {
        "description": "Schedule recurring performance briefs delivered to your inbox.\n\n**What it does:**\n- Creates automated daily or weekly performance reports\n- Summarizes key metrics across all your ad platforms\n- Highlights top performers and underperformers\n- Provides AI-generated recommendations\n\n**When to use:**\n- \"Set up a daily performance report\"\n- \"Send me weekly campaign summaries\"\n- \"I want automated performance updates\"\n- \"Create a scheduled brief for my campaigns\"\n\n**Execution time:** 2-3 seconds to schedule\n\n**Example:**\nUser: \"Send me daily performance updates at 9 AM\"\n\u2192 Call schedule_brief with:\n  - name: \"Daily Performance Update\"\n  - schedule_type: \"daily\"\n  - time: \"09:00\"\n  - delivery_method: \"email\"\n  - delivery_destination: \"user@example.com\"",
        "operationId": "execute_schedule_brief",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "delivery_destination": "https://example.com",
                  "delivery_method": "email",
                  "name": "string",
                  "platforms": [
                    "string"
                  ],
                  "schedule_type": "weekly",
                  "time": "09:00",
                  "timezone": "America/New_York"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for scheduling a performance brief.",
                    "properties": {
                      "delivery_destination": {
                        "description": "Email address, Slack channel, or webhook URL",
                        "title": "Delivery Destination",
                        "type": "string"
                      },
                      "delivery_method": {
                        "default": "email",
                        "description": "How to deliver: 'email', 'slack', or 'webhook'",
                        "title": "Delivery Method",
                        "type": "string"
                      },
                      "name": {
                        "description": "Name for this scheduled brief (e.g., 'Daily Performance Summary')",
                        "title": "Name",
                        "type": "string"
                      },
                      "platforms": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Platforms to include: 'google_ads', 'meta_ads', 'tiktok_ads', 'linkedin_ads'. Default is all connected.",
                        "title": "Platforms"
                      },
                      "schedule_type": {
                        "default": "weekly",
                        "description": "How often to send: 'daily', 'weekly', or 'every_n_days'",
                        "title": "Schedule Type",
                        "type": "string"
                      },
                      "time": {
                        "default": "09:00",
                        "description": "Time to send (24-hour format, e.g., '09:00' for 9 AM)",
                        "title": "Time",
                        "type": "string"
                      },
                      "timezone": {
                        "default": "America/New_York",
                        "description": "Timezone (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo')",
                        "title": "Timezone",
                        "type": "string"
                      }
                    },
                    "required": [
                      "name",
                      "delivery_destination"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for schedule_brief)"
                  },
                  "success": true,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: schedule_brief",
                  "is_error": true,
                  "success": false,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "schedule_brief"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Schedule recurring performance briefs delivered to your inbox [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "schedule_brief"
      }
    },
    "/api/v1/tools/search_audiences/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nSearch for available audience segments (in-market, affinity, custom).\n\nUse this tool to discover audience segment IDs for:\n- **PMax campaigns:** Use IDs with `add_pmax_audience_signal` (audience signals/hints)\n- **Demand Gen / YouTube campaigns:** Pass IDs via `audience_segments` parameter in `create_demandgen_campaign` or `create_youtube_campaign` (direct targeting)\n\n**Audience types:**\n- **In-market:** Users actively researching products/services (e.g., \"Business Software\")\n- **Affinity:** Users with long-term interests (e.g., \"Technology Enthusiasts\")\n- **Custom:** Your account's existing remarketing/customer match lists\n\n**Parameters:**\n- query: Search term (e.g., \"advertising services\", \"SaaS tools\")\n\n**Execution time:** 2-5 seconds",
        "operationId": "execute_search_audiences",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "customer_id": "string",
                  "query": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for searching available audiences.",
                    "properties": {
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "query": {
                        "description": "Search query for discovering audiences. Example: 'business software', 'advertising services', 'technology'",
                        "title": "Query",
                        "type": "string"
                      }
                    },
                    "required": [
                      "query"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for search_audiences)"
                  },
                  "success": true,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: search_audiences",
                  "is_error": true,
                  "success": false,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "search_audiences"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "search_audiences"
      }
    },
    "/api/v1/tools/search_linkedin_targeting/execute": {
      "post": {
        "description": "User needs to find targeting URNs for LinkedIn campaigns.\n\nSearch for targeting entities like job titles, industries, skills, locations, etc.\n\nFacet Types:\n- job_titles, industries, skills, locations\n- seniorities, company_sizes, job_functions\n- interests, degrees, fields_of_study\n- employers, groups\n\nReturns:\n- List of matching entities with URNs and names\n- URNs can be used in campaign targeting\n\nExample Prompts:\n- \"Find LinkedIn targeting for software engineers\"\n- \"Search for marketing job titles on LinkedIn\"\n- \"Find LinkedIn industry targeting for SaaS\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_search_linkedin_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "facet_type": "string",
                  "limit": 25,
                  "query": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for searching targeting entities",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "facet_type": {
                        "description": "Type of targeting to search: job_titles, industries, skills, locations, seniorities, company_sizes, job_functions, interests, etc.",
                        "title": "Facet Type",
                        "type": "string"
                      },
                      "limit": {
                        "default": 25,
                        "description": "Maximum results to return (default: 25)",
                        "maximum": 100,
                        "minimum": 1,
                        "title": "Limit",
                        "type": "integer"
                      },
                      "query": {
                        "description": "Search query",
                        "title": "Query",
                        "type": "string"
                      }
                    },
                    "required": [
                      "facet_type",
                      "query"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for search_linkedin_targeting)"
                  },
                  "success": true,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: search_linkedin_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "search_linkedin_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User needs to find targeting URNs for LinkedIn campaigns",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "search_linkedin_targeting"
      }
    },
    "/api/v1/tools/search_meta_targeting/execute": {
      "post": {
        "description": "User wants to find targeting options for their Meta (Facebook/Instagram) ad campaigns.\n\nThis tool searches the Meta Marketing API to find targeting options including interests, behaviors, demographics, locations, and more.\n\nReturns:\n- List of targeting options with IDs, names, and audience sizes\n- Category/path information for interests and behaviors\n- Location details including country, region for geo-targeting\n\nWhen to use this tool:\n- \"Find interests related to fitness\"\n- \"What targeting options are available for travel?\"\n- \"Search for locations in California\"\n- \"Find behaviors for online shoppers\"\n- \"What demographics can I target?\"\n- \"Find job titles for marketing professionals\"\n- \"Search for schools like Harvard\"\n\nSearch Types Available:\n- interest: Topics and activities (e.g., 'fitness', 'cooking', 'travel')\n- behavior: User behaviors (e.g., 'frequent travelers', 'online shoppers')\n- demographic: Demographics (e.g., 'new parents', 'college educated')\n- life_event: Life events (e.g., 'recently moved', 'newly engaged')\n- location: Geo-targeting (e.g., 'New York', 'California', '90210')\n- locale: Language targeting (e.g., 'Spanish', 'French')\n- employer: Employer targeting (e.g., 'Google', 'Microsoft')\n- job_title: Job title targeting (e.g., 'Software Engineer')\n- school: Education school targeting (e.g., 'Harvard', 'Stanford')\n- major: Education major targeting (e.g., 'Computer Science')\n\nParameters:\n- search_type: Type of targeting to search (required)\n- query: Search query string (required)\n- limit: Maximum results (1-100, default: 50)\n- locale: Locale for results (default: en_US)\n- location_types: For location search - filter by types (country, region, city, zip)\n- country_code: For location search - filter by country (e.g., 'US')\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 1-3 seconds\nData source: Meta Marketing API Targeting Search",
        "operationId": "execute_search_meta_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "country_code": "string",
                  "limit": 50,
                  "locale": "en_US",
                  "location_types": [
                    "string"
                  ],
                  "query": "string",
                  "search_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for Meta targeting search",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "country_code": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "For location search only: filter results to a specific country (e.g., 'US', 'GB', 'CA')",
                        "title": "Country Code"
                      },
                      "limit": {
                        "default": 50,
                        "description": "Maximum number of results to return (1-100). Default is 50.",
                        "title": "Limit",
                        "type": "integer"
                      },
                      "locale": {
                        "default": "en_US",
                        "description": "Locale for results (e.g., 'en_US', 'es_ES', 'fr_FR'). Default is 'en_US'.",
                        "title": "Locale",
                        "type": "string"
                      },
                      "location_types": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "For location search only: filter by location types. Options: 'country', 'region', 'city', 'zip', 'geo_market', 'place'. Example: ['city', 'region']",
                        "title": "Location Types"
                      },
                      "query": {
                        "description": "Search query string (e.g., 'fitness' for interests, 'New York' for locations)",
                        "title": "Query",
                        "type": "string"
                      },
                      "search_type": {
                        "description": "Type of targeting to search for:\n- 'interest': Find interests for targeting (e.g., 'fitness', 'cooking', 'travel')\n- 'behavior': Find behaviors for targeting (e.g., 'frequent travelers', 'online shoppers')\n- 'demographic': Find demographic targeting options (e.g., 'new parents', 'college educated')\n- 'life_event': Find life event targeting (e.g., 'recently moved', 'newly engaged')\n- 'location': Find locations for geo-targeting (e.g., 'New York', 'California', '90210')\n- 'locale': Find language targeting options (e.g., 'Spanish', 'French')\n- 'employer': Find employer targeting (e.g., 'Google', 'Microsoft')\n- 'job_title': Find job title targeting (e.g., 'Software Engineer', 'Marketing Manager')\n- 'school': Find education school targeting (e.g., 'Harvard', 'Stanford')\n- 'major': Find education major targeting (e.g., 'Computer Science', 'Business')",
                        "title": "Search Type",
                        "type": "string"
                      }
                    },
                    "required": [
                      "search_type",
                      "query"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for search_meta_targeting)"
                  },
                  "success": true,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: search_meta_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "search_meta_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to find targeting options for their Meta (Facebook/Instagram) ad campaigns",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "search_meta_targeting"
      }
    },
    "/api/v1/tools/search_tiktok_targeting/execute": {
      "post": {
        "description": "Search TikTok targeting options for campaign creation and ad group management.\n\nIMPORTANT: Call this tool to find valid targeting IDs BEFORE creating campaigns or ad groups.\n\nCovers ALL targeting types:\n- interest_categories: Browse all interest categories (fashion, gaming, food, etc.) \u2014 returns IDs for interest_category_ids\n- interest_keywords: Search interest keywords by a seed keyword (e.g., \"fitness\") \u2014 returns keyword IDs for interest_keyword_ids\n- regions: Get available locations (countries, provinces, cities) \u2014 returns location IDs for location_ids/target_locations\n- languages: Get available language codes \u2014 returns codes for languages targeting\n- action_categories: Get app event categories \u2014 for conversion tracking\n- carriers: Get mobile carrier names \u2014 for carrier targeting\n- device_models: Get device models \u2014 for device targeting\n\nCommon usage patterns:\n- Before create_tiktok_campaign: search regions + interest_categories to set targeting\n- Before add_tiktok_ad_group: search interest_keywords for new audience segments\n- Before update_tiktok_ad_group: search regions to expand/narrow location targeting\n\nParameters:\n- targeting_type: REQUIRED \u2014 which type to search (see above)\n- keyword: REQUIRED for interest_keywords \u2014 seed keyword to search\n- level_range: For regions \u2014 TO_COUNTRY (default), TO_PROVINCE, TO_CITY, TO_DISTRICT\n- language: Display language for results (default: en)",
        "operationId": "execute_search_tiktok_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "audience_type": "GENERAL_INTEREST",
                  "keyword": "string",
                  "language": "en",
                  "limit": 50,
                  "mode": "FUZZ_MATCH",
                  "placements": [
                    "string"
                  ],
                  "targeting_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for searching TikTok targeting options",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID (optional).",
                        "title": "Advertiser Id"
                      },
                      "audience_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "GENERAL_INTEREST",
                        "description": "Audience type for interest_keywords: 'GENERAL_INTEREST' (default) or 'PURCHASE_INTENTION'.",
                        "title": "Audience Type"
                      },
                      "keyword": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Seed keyword for interest_keywords search. Required when targeting_type='interest_keywords'.",
                        "title": "Keyword"
                      },
                      "language": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "en",
                        "description": "Language for results. Default: 'en'. Options: en, zh, ja, de, es, fr, id, it, ko, ru, th, tr, vi, ar, pt, ms.",
                        "title": "Language"
                      },
                      "level_range": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "TO_COUNTRY",
                        "description": "Region detail level: 'ALL', 'TO_COUNTRY', 'TO_PROVINCE', 'TO_CITY', 'TO_DISTRICT'. Default: TO_COUNTRY.",
                        "title": "Level Range"
                      },
                      "limit": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": 50,
                        "description": "Max results for interest_keywords (1-50). Default: 50.",
                        "title": "Limit"
                      },
                      "mode": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "FUZZ_MATCH",
                        "description": "Search mode for interest_keywords: 'FUZZ_MATCH' (default) or 'SEMANTIC_RECOMMEND'.",
                        "title": "Mode"
                      },
                      "objective_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "TRAFFIC",
                        "description": "Campaign objective for regions lookup. Default: TRAFFIC.",
                        "title": "Objective Type"
                      },
                      "operating_system": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "OS filter for regions: 'ANDROID' or 'IOS'. Optional.",
                        "title": "Operating System"
                      },
                      "placements": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Placements filter: ['PLACEMENT_TIKTOK'], ['PLACEMENT_PANGLE'], etc. Default: TikTok only.",
                        "title": "Placements"
                      },
                      "targeting_type": {
                        "description": "Type of targeting to search. Options: 'interest_categories' (browse all interest categories), 'interest_keywords' (search keywords by seed keyword \u2014 requires 'keyword'), 'regions' (available locations \u2014 countries, provinces, cities), 'languages' (available language codes), 'action_categories' (app event categories), 'carriers' (mobile carriers), 'device_models' (target device models).",
                        "title": "Targeting Type",
                        "type": "string"
                      }
                    },
                    "required": [
                      "targeting_type"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for search_tiktok_targeting)"
                  },
                  "success": true,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: search_tiktok_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "search_tiktok_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Search TikTok targeting options for campaign creation and ad group management",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "search_tiktok_targeting"
      }
    },
    "/api/v1/tools/select_google_campaign_type/execute": {
      "post": {
        "description": "**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).\n\n**IMPORTANT:** This tool should be called BEFORE any keyword research, asset discovery, or campaign creation when the user says things like:\n- \"Create a Google Ads campaign\"\n- \"I want to run Google Ads\"\n- \"Set up a Google advertising campaign\"\n- \"Help me create ads on Google\"\n- \"I want to advertise on Google\"\n- \"Create a campaign\"\n- \"Run ads\"\n\nThis tool guides the user to select their campaign TYPE, then provides a detailed workflow for that specific type.\n\n**Campaign Types Available:**\n1. **search** - Text ads in Google Search results (best for high-intent keywords, lead gen, local services)\n2. **pmax** - Performance Max across all Google channels (best for ecommerce, brand awareness, multi-channel reach)\n3. **youtube** - Video ads on YouTube (best for video content, brand storytelling, product demos)\n\n**Returns:**\n- What the campaign type is best for\n- Step-by-step workflow with which tools to call\n- Requirements (character limits, assets needed, etc.)\n- Natural follow-up question to start the creation flow\n\n**Do NOT use this tool if:**\n- User specifically asks for \"search campaign\" or \"text ads\" \u2192 go directly to research_keywords then create_search_campaign\n- User specifically asks for \"PMax\" or \"Performance Max\" \u2192 go directly to discover_existing_assets then create_pmax_campaign\n- User specifically asks for \"YouTube campaign\" or \"video campaign\" \u2192 go directly to validate_video then create_youtube_campaign\n- User is asking about performance/analytics \u2192 use get_campaign_performance\n- User is asking about existing campaigns \u2192 use list_campaigns\n\n**Parameters:**\n- campaign_type: 'search', 'pmax', or 'youtube'\n\n**Execution time:** Instant (no API call)",
        "operationId": "execute_select_google_campaign_type",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for selecting Google Ads campaign type",
                    "properties": {
                      "campaign_type": {
                        "description": "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)",
                        "title": "Campaign Type",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_type"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for select_google_campaign_type)"
                  },
                  "success": true,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: select_google_campaign_type",
                  "is_error": true,
                  "success": false,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "select_google_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "**USE THIS TOOL FIRST WHEN:** User wants to create a Google Ads campaign but hasn't specified the campaign type (Sear...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "select_google_campaign_type"
      }
    },
    "/api/v1/tools/select_linkedin_campaign_type/execute": {
      "post": {
        "description": "User wants to create a LinkedIn ad campaign but hasn't specified the campaign type (image, video, carousel, or text).\n\nIMPORTANT: This tool should be called BEFORE any asset discovery or campaign creation when the user says things like:\n- \"Create a LinkedIn campaign\"\n- \"Run LinkedIn ads\"\n- \"I want to advertise on LinkedIn\"\n- \"Set up a LinkedIn advertising campaign\"\n- \"Help me create ads on LinkedIn\"\n\nThis tool asks the user what TYPE of campaign they want to create, then provides guidance on the next steps including creative quantity best practices.\n\nCampaign Types Available:\n1. image \u2014 Single image ad in the feed (most common, 4-5 ad variations recommended)\n2. video \u2014 Video ad in the feed (good for demos, storytelling, 3-4 variations recommended)\n3. carousel \u2014 2-10 swipeable image cards (good for multi-product showcase, 3-5 cards recommended)\n4. text \u2014 Simple desktop right-rail/top-banner ad (budget-friendly, 3-4 variations recommended)\n\nReturns:\n- Confirmation of selected campaign type\n- Specific requirements and creative specs for that type\n- Recommended number of ad variations per LinkedIn best practices\n- Next steps and which tool to use\n\nDo NOT use this tool if:\n- User specifically asks for \"image campaign\" / \"single image\" - go directly to image workflow\n- User specifically asks for \"video campaign\" / \"video ad\" - go directly to video workflow\n- User specifically asks for \"carousel\" / \"multiple images\" - go directly to carousel workflow\n- User specifically asks for \"text ad\" / \"simple ad\" - go directly to text workflow\n- User is asking about performance/analytics - use performance analysis tools\n\nParameters:\n- campaign_type: 'image', 'video', 'carousel', or 'text'\n\nExecution time: Instant (no API call)",
        "operationId": "execute_select_linkedin_campaign_type",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for selecting LinkedIn campaign type",
                    "properties": {
                      "campaign_type": {
                        "description": "Type of LinkedIn campaign to create. Options: 'image' (single image ad), 'video' (video ad), 'carousel' (2-10 swipeable cards), 'text' (desktop text ad)",
                        "title": "Campaign Type",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_type"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for select_linkedin_campaign_type)"
                  },
                  "success": true,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: select_linkedin_campaign_type",
                  "is_error": true,
                  "success": false,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "select_linkedin_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a LinkedIn ad campaign but hasn't specified the campaign type (image, video, carousel, or text)",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "select_linkedin_campaign_type"
      }
    },
    "/api/v1/tools/select_meta_campaign_type/execute": {
      "post": {
        "description": "User wants to create a Meta (Facebook/Instagram) ad campaign but hasn't specified the campaign type (image, video, or carousel).\n\nIMPORTANT: This tool should be called BEFORE any asset discovery or campaign creation when the user says things like:\n- \"Create a Meta campaign\"\n- \"Create a Facebook ad\"\n- \"I want to run Instagram ads\"\n- \"Set up a Meta advertising campaign\"\n- \"Help me create ads on Facebook\"\n\nThis tool asks the user what TYPE of campaign they want to create, then provides guidance on the next steps.\n\nCampaign Types Available:\n1. image - Single image ad (most common, good for static visuals)\n2. video - Video ad including Reels (good for engagement, storytelling)\n3. carousel - 2-10 swipeable cards (good for showcasing multiple products)\n\nDo NOT use this tool if:\n- User specifically asks for \"image campaign\" - use create_meta_image_campaign\n- User specifically asks for \"video campaign\" or \"Reels\" - use create_meta_video_campaign\n- User specifically asks for \"carousel campaign\" - use create_meta_carousel_campaign\n- User is asking about performance/analytics - use performance analysis tools\n\nParameters:\n- campaign_type: 'image', 'video', or 'carousel'",
        "operationId": "execute_select_meta_campaign_type",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for selecting Meta campaign type",
                    "properties": {
                      "campaign_type": {
                        "description": "Type of Meta campaign to create. Options: 'image' (single image ad), 'video' (video/Reels ad), 'carousel' (2-10 swipeable cards)",
                        "title": "Campaign Type",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_type"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for select_meta_campaign_type)"
                  },
                  "success": true,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: select_meta_campaign_type",
                  "is_error": true,
                  "success": false,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "select_meta_campaign_type"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to create a Meta (Facebook/Instagram) ad campaign but hasn't specified the campaign type (image, video, or...",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "select_meta_campaign_type"
      }
    },
    "/api/v1/tools/start_research/execute": {
      "post": {
        "description": "Start an AI-powered research job (runs in background).\n\n**What it does:**\n- Performs deep analysis that takes 5 minutes to 2 hours\n- Runs asynchronously - you'll be notified when complete\n- Provides comprehensive insights and recommendations\n\n**Research Types:**\n\n1. **competitor_analysis** - Analyze competitor ad strategies\n   Required context: competitors (list of competitor domains/names)\n\n2. **keyword_research** - Discover high-value keywords\n   Required context: seed_keywords (list of starting keywords)\n\n3. **market_landscape** - Industry overview and trends\n   Required context: industry (industry name)\n\n4. **audience_insights** - Target audience analysis\n   Required context: target_audience (description of audience)\n\n5. **campaign_strategy** - Strategic recommendations\n   Required context: campaign_goals (what you want to achieve)\n\n**Depth Levels:**\n- quick: 5-10 minutes\n- standard: 15-30 minutes\n- comprehensive: 1-2 hours\n\n**Example:**\nUser: \"Research my competitors\"\n\u2192 Call start_research with:\n  - research_type: \"competitor_analysis\"\n  - depth: \"standard\"\n  - context: {\"competitors\": [\"competitor1.com\", \"competitor2.com\"]}",
        "operationId": "execute_start_research",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "context": {
                    "goals": [
                      "increase brand awareness",
                      "drive purchases"
                    ],
                    "industry": "ecommerce",
                    "target_audience": "runners aged 25-45"
                  },
                  "depth": "standard",
                  "notify_destination": "string",
                  "notify_method": "email",
                  "research_type": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for starting a research job.",
                    "properties": {
                      "context": {
                        "additionalProperties": true,
                        "description": "Research context (varies by type). See tool description for required fields.",
                        "title": "Context",
                        "type": "object"
                      },
                      "depth": {
                        "default": "standard",
                        "description": "Depth: 'quick' (5-10 min), 'standard' (15-30 min), 'comprehensive' (1-2 hours)",
                        "title": "Depth",
                        "type": "string"
                      },
                      "notify_destination": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Email address or Slack channel for notification",
                        "title": "Notify Destination"
                      },
                      "notify_method": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "email",
                        "description": "How to notify when complete: 'email', 'slack', 'in_app'",
                        "title": "Notify Method"
                      },
                      "research_type": {
                        "description": "Type: 'competitor_analysis', 'keyword_research', 'market_landscape', 'audience_insights', 'campaign_strategy'",
                        "title": "Research Type",
                        "type": "string"
                      }
                    },
                    "required": [
                      "research_type",
                      "context"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for start_research)"
                  },
                  "success": true,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: start_research",
                  "is_error": true,
                  "success": false,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "start_research"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Start an AI-powered research job (runs in background) [write]",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "start_research"
      }
    },
    "/api/v1/tools/suggest_ad_content/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nGenerate AI-suggested headlines and descriptions based on campaign keywords.\n\nAnalyzes campaign keywords and generates optimized ad content following creative guidelines:\n- 15 headlines across 3 categories (What It Is, Benefits, Pain/Proof/CTA)\n- 4 descriptions with value props and CTAs\n- All validated for character limits\n\n**Parameters:**\n- campaign_id: The campaign to analyze (REQUIRED)\n- business_description: Optional context about the business\n- key_benefits: Optional list of benefits (e.g., [\"fast setup\", \"no dashboard\"])\n- proof_points: Optional proof points (e.g., [{\"number\": \"2000+\", \"metric\": \"campaigns\"}])\n- pain_points: Optional pain points (e.g., [\"manual ads\", \"complex UI\"])\n- customer_id: Optional\n\n**Returns:**\n- suggested_headlines: 15 headlines ready to use\n- suggested_descriptions: 4 descriptions ready to use\n- headline_categories: Headlines organized by category\n- keyword_themes: Top themes from campaign keywords\n\n**Use this when:**\n- User wants fresh ad copy ideas\n- User asks \"what should my headlines be?\"\n- Starting a new A/B test\n- Refreshing stale ad content\n\n**Execution time:** 3-5 seconds\n\n**Example:**\nUser: \"Suggest some better headlines for my campaign\"\nAgent:\n1. Uses suggest_ad_content to generate ideas\n2. Shows suggestions organized by category\n3. User picks favorites\n4. Uses update_ad_headlines to apply selected headlines",
        "operationId": "execute_suggest_ad_content",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "business_description": "string",
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "key_benefits": [
                    "string"
                  ],
                  "pain_points": [
                    "string"
                  ],
                  "proof_points": [
                    "5-star rated on Trustpilot (4,200 reviews)",
                    "Free shipping over $50"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for AI-powered ad content suggestions",
                    "properties": {
                      "business_description": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Description of what the business does. Helps generate better suggestions.",
                        "title": "Business Description"
                      },
                      "campaign_id": {
                        "description": "The campaign ID to analyze for keyword themes. Use list_campaigns first.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "key_benefits": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: List of key benefits (e.g., ['30 second launch', 'no dashboard required']).",
                        "title": "Key Benefits"
                      },
                      "pain_points": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Pain points customers face (e.g., ['manual ads', 'complex UI']).",
                        "title": "Pain Points"
                      },
                      "proof_points": {
                        "anyOf": [
                          {
                            "items": {
                              "additionalProperties": {
                                "type": "string"
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Proof points. Each with 'number' and 'metric' (e.g., {'number': '2000+', 'metric': 'campaigns'}).",
                        "title": "Proof Points"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for suggest_ad_content)"
                  },
                  "success": true,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: suggest_ad_content",
                  "is_error": true,
                  "success": false,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "suggest_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e...",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "suggest_ad_content"
      }
    },
    "/api/v1/tools/switch_primary_account/execute": {
      "post": {
        "description": "Activate one or more ad accounts for a platform.\n\nActivated accounts are available for all ad operations. Pass a single account_id\nto activate one account, or pass account_ids (list) to activate multiple accounts\nfor multi-account management.\n\nUse this tool when:\n- User wants to switch to a different ad account\n- User says \"use my other account\" or \"activate these accounts\"\n- User wants to manage multiple accounts simultaneously\n- User specifies which accounts to work with\n\nAccepts either account_id (single) or account_ids (list of IDs from list_connected_accounts).\n\nIMPORTANT: Returns updated connections list after switching.",
        "operationId": "execute_switch_primary_account",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "account_ids": [
                    "string"
                  ],
                  "platform": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for switch_primary_account tool",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Single account ID to activate (e.g., '1234567890' for Google Ads). Use this OR account_ids, not both.",
                        "title": "Account Id"
                      },
                      "account_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Account Ids"
                      },
                      "platform": {
                        "description": "Platform to switch primary account for: google_ads, tiktok_ads, meta_ads, or linkedin_ads",
                        "title": "Platform",
                        "type": "string"
                      }
                    },
                    "required": [
                      "platform"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for switch_primary_account)"
                  },
                  "success": true,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: switch_primary_account",
                  "is_error": true,
                  "success": false,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "switch_primary_account"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Activate one or more ad accounts for a platform [write]",
        "tags": [
          "general"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "switch_primary_account"
      }
    },
    "/api/v1/tools/test_monitor/execute": {
      "post": {
        "description": "Dry-run a monitor against current data WITHOUT triggering alerts or sending notifications.\n\n**What it does:**\n- Evaluates the monitor's conditions against current campaign data\n- Shows which campaigns would match and which would be skipped\n- Does NOT create triggers, send emails, or queue auto-actions\n- Useful for verifying a monitor is configured correctly before enabling\n\n**When to use:**\n- \"Test my CPA monitor\"\n- \"Would this alert trigger right now?\"\n- \"Check if my monitor is set up correctly\"\n- \"Dry run the budget overspend alert\"\n\nRequires the monitor's alert_id (get from list_monitors).",
        "operationId": "execute_test_monitor",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "alert_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for dry-running a monitor against current data.",
                    "properties": {
                      "alert_id": {
                        "description": "ID of the monitoring alert to test",
                        "title": "Alert Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "alert_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for test_monitor)"
                  },
                  "success": true,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: test_monitor",
                  "is_error": true,
                  "success": false,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "test_monitor"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Dry-run a monitor against current data WITHOUT triggering alerts or sending notifications",
        "tags": [
          "monitoring"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "test_monitor"
      }
    },
    "/api/v1/tools/update_ad_content/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nCombined update for ad content (headlines, descriptions, and/or final URLs).\n\nUse this for efficiency when updating multiple ad elements at once.\nSingle API call with combined field mask.\n\n**Parameters:**\n- ad_id: The ad ID to update (REQUIRED)\n- ad_group_id: The ad group ID (REQUIRED)\n- headlines: Optional list of 3-15 headlines (max 30 chars each)\n- descriptions: Optional list of 2-4 descriptions (max 90 chars each)\n- final_urls: Optional list of landing page URLs\n- customer_id: Optional\n\nAt least one of headlines, descriptions, or final_urls is required.\n\n**Execution time:** 2-4 seconds\n\n**Example:**\nUser: \"Update both headlines and descriptions for my ad\"\nAgent: Uses update_ad_content with both headlines and descriptions in one call",
        "operationId": "execute_update_ad_content",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "ad_id": "string",
                  "customer_id": "string",
                  "descriptions": [
                    "string"
                  ],
                  "final_urls": [
                    "string"
                  ],
                  "headlines": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for combined ad content update",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the ad belongs to. Get from get_campaign_structure.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "ad_id": {
                        "description": "The ad ID to update. Get from get_campaign_structure.",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: 2-4 descriptions, each max 90 characters.",
                        "title": "Descriptions"
                      },
                      "final_urls": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: Landing page URLs.",
                        "title": "Final Urls"
                      },
                      "headlines": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Optional: 3-15 headlines, each max 30 characters.",
                        "title": "Headlines"
                      }
                    },
                    "required": [
                      "ad_id",
                      "ad_group_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_ad_content)"
                  },
                  "success": true,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_ad_content",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_content"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_ad_content"
      }
    },
    "/api/v1/tools/update_ad_descriptions/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nUpdate descriptions for a Responsive Search Ad (RSA).\n\n**Parameters:**\n- ad_id: The ad ID to update (REQUIRED - get from get_campaign_structure)\n- ad_group_id: The ad group ID (REQUIRED)\n- descriptions: List of 2-4 descriptions (REQUIRED)\n- customer_id: Optional\n\n**Description Rules:**\n- Max 90 characters per description\n- 2-4 descriptions required\n- Descriptions should expand on headlines\n- Include specific details and CTAs\n- Formula: [Value Prop]. [Specific Detail]. [CTA].\n\n**Execution time:** 2-4 seconds\n\n**WARNING:** Updated ads go through Google's review process.\n\n**Example:**\nUser: \"Make my ad descriptions more action-oriented\"\nAgent:\n1. Uses get_campaign_structure to find ad_id\n2. Shows current descriptions\n3. Prepares new descriptions with CTAs\n4. Gets approval and updates",
        "operationId": "execute_update_ad_descriptions",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "ad_id": "string",
                  "customer_id": "string",
                  "descriptions": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating ad descriptions",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the ad belongs to. Get from get_campaign_structure.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "ad_id": {
                        "description": "The ad ID to update. Get from get_campaign_structure.",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "descriptions": {
                        "description": "List of 2-4 descriptions. Each description max 90 characters. Descriptions should expand on headlines and include CTAs.",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 4,
                        "minItems": 2,
                        "title": "Descriptions",
                        "type": "array"
                      }
                    },
                    "required": [
                      "ad_id",
                      "ad_group_id",
                      "descriptions"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_ad_descriptions)"
                  },
                  "success": true,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_ad_descriptions",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_descriptions"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_ad_descriptions"
      }
    },
    "/api/v1/tools/update_ad_headlines/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nUpdate headlines for a Responsive Search Ad (RSA).\n\n**MANDATORY WORKFLOW:**\n1. Call `get_campaign_structure` to find ad_id and ad_group_id\n2. Review current headlines to understand what's there\n3. Prepare 3-15 new headlines following the category system:\n   - Category A (5): What It Is (product/feature identity)\n   - Category B (6): Benefits/Outcomes\n   - Category C (4): Pain Points, Proof & CTAs\n4. Validate headlines (max 30 chars each, no \"!\" marks, no unverified superlatives)\n5. Get user approval before updating\n6. Call this tool with ad_id, ad_group_id, and headlines\n\n**Headline Rules (from creative guidelines):**\n- Max 30 characters per headline\n- 3-15 headlines required\n- Headlines should work in ANY combination\n- Avoid exclamation marks (may trigger Google disapproval)\n- Avoid unverified superlatives (\"Best\", \"#1\", \"Leading\", \"Top\")\n- Any random pair should make logical sense together\n\n**Parameters:**\n- ad_id: The ad ID to update (REQUIRED)\n- ad_group_id: The ad group ID (REQUIRED)\n- headlines: List of 3-15 headlines (REQUIRED)\n- customer_id: Optional\n\n**Execution time:** 2-4 seconds\n\n**WARNING:** Updated ads go through Google's review process (typically 24-48 hours).\n\n**Example:**\nUser: \"Update my ad headlines to be more compelling\"\nAgent:\n1. Uses get_campaign_structure to find ad_id and ad_group_id\n2. Shows current headlines to user\n3. Prepares new headlines across all 3 categories\n4. Gets user approval\n5. Uses update_ad_headlines with approved headlines",
        "operationId": "execute_update_ad_headlines",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "ad_id": "string",
                  "customer_id": "string",
                  "headlines": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating ad headlines",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the ad belongs to. Get from get_campaign_structure.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "ad_id": {
                        "description": "The ad ID to update. Get from get_campaign_structure (look in ad_groups -> ads section).",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "headlines": {
                        "description": "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.).",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 15,
                        "minItems": 3,
                        "title": "Headlines",
                        "type": "array"
                      }
                    },
                    "required": [
                      "ad_id",
                      "ad_group_id",
                      "headlines"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_ad_headlines)"
                  },
                  "success": true,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_ad_headlines",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_ad_headlines"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_ad_headlines"
      }
    },
    "/api/v1/tools/update_bid_strategy/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nChange campaign bidding strategy.\n\n**Available Strategies:**\n- MAXIMIZE_CLICKS: Get as many clicks as possible within budget\n- MAXIMIZE_CONVERSIONS: Get as many conversions as possible\n- TARGET_CPA: Set a target cost per acquisition (requires target_cpa)\n- TARGET_ROAS: Set a target return on ad spend (requires target_roas)\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED)\n- strategy: MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, TARGET_CPA, or TARGET_ROAS (REQUIRED)\n- target_cpa: Required if strategy is TARGET_CPA. In dollars, e.g., 25.00 for $25 CPA\n- target_roas: Required if strategy is TARGET_ROAS. Multiplier, e.g., 4.0 for 400% ROAS\n- customer_id: Optional (uses connected account if omitted)\n\n**IMPORTANT:**\n- TARGET_CPA requires conversion tracking to be set up\n- TARGET_ROAS requires conversion value tracking\n- Changing strategy may take 1-2 weeks to stabilize performance\n- Confirm with user before changing\n\n**Execution time:** 2-4 seconds\n\n**Example:**\nUser: \"I want to target $30 CPA\"\nAgent:\n1. Confirms: \"I'll change the bidding strategy to TARGET_CPA with a $30 target. This may take 1-2 weeks to optimize. Proceed?\"\n2. Uses update_bid_strategy with strategy=TARGET_CPA, target_cpa=30.00",
        "operationId": "execute_update_bid_strategy",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "strategy": "string",
                  "target_cpa": 1.0,
                  "target_roas": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating campaign bidding strategy",
                    "properties": {
                      "campaign_id": {
                        "description": "The campaign ID to update. Use list_campaigns first to get available campaign IDs.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "strategy": {
                        "description": "Bidding strategy: MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA, or TARGET_ROAS",
                        "title": "Strategy",
                        "type": "string"
                      },
                      "target_cpa": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target CPA in dollars (required if strategy is TARGET_CPA). Example: 25.00 for $25 CPA",
                        "title": "Target Cpa"
                      },
                      "target_roas": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Target ROAS multiplier (required if strategy is TARGET_ROAS). Example: 4.0 for 400% ROAS",
                        "title": "Target Roas"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "strategy"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_bid_strategy)"
                  },
                  "success": true,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_bid_strategy",
                  "is_error": true,
                  "success": false,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_bid_strategy"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_bid_strategy"
      }
    },
    "/api/v1/tools/update_campaign/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nUpdate an existing campaign's settings.\n\n**Can update:**\n- name: Campaign name\n- status: ENABLED or PAUSED\n- budget_amount: Daily budget in dollars\n\n**IMPORTANT:**\n- Confirm with user BEFORE making changes\n- Use list_campaigns first to get campaign_id\n- At least one field (name, status, or budget_amount) is required\n\n**Parameters:**\n- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)\n- name: New campaign name (optional)\n- status: ENABLED or PAUSED (optional)\n- budget_amount: Daily budget in dollars, e.g., 50.00 for $50/day (optional)\n- customer_id: Optional (uses connected account if omitted)\n\n**Execution time:** 2-4 seconds\n\n**Example:**\nUser: \"Increase the budget to $100/day\"\nAgent:\n1. Uses list_campaigns to find the campaign\n2. Confirms with user: \"I'll update 'Summer Sale' budget from $50 to $100/day. Proceed?\"\n3. Uses update_campaign with campaign_id and budget_amount=100.00",
        "operationId": "execute_update_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "budget_amount": 1.0,
                  "campaign_id": "<campaign_id>",
                  "customer_id": "string",
                  "name": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating campaign settings",
                    "properties": {
                      "budget_amount": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget in dollars, e.g., 50.00 for $50/day (optional)",
                        "title": "Budget Amount"
                      },
                      "campaign_id": {
                        "description": "The campaign ID to update. Use list_campaigns first to get available campaign IDs.",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New campaign name (optional)",
                        "title": "Name"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: ENABLED or PAUSED (optional)",
                        "title": "Status"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_campaign)"
                  },
                  "success": true,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_campaign"
      }
    },
    "/api/v1/tools/update_keyword/execute": {
      "post": {
        "description": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL error message to the user (it contains the upgrade link)\n- DO NOT attempt to work around the error or use alternative data\n- DO NOT create campaigns or perform actions without valid tool data\n- STOP and direct the user to upgrade via the provided link\n\nUpdate keyword bid or status.\n\n**Parameters:**\n- keyword_id: The keyword to update (REQUIRED - get from get_campaign_structure)\n- ad_group_id: The ad group containing the keyword (REQUIRED)\n- cpc_bid_micros: New bid in micros (1 USD = 1,000,000). Example: 2500000 = $2.50\n- status: ENABLED or PAUSED\n- customer_id: Optional\n\n**Can update:**\n- Bid amount (cpc_bid_micros)\n- Status (ENABLED/PAUSED)\n\n**CANNOT update (immutable):**\n- Keyword text\n- Match type\n\n**Execution time:** 2-3 seconds\n\n**Example:**\nUser: \"Increase the bid on 'running shoes' to $3\"\nAgent:\n1. Uses get_campaign_structure to find the keyword_id\n2. Uses update_keyword with cpc_bid_micros=3000000",
        "operationId": "execute_update_keyword",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_group_id": "string",
                  "cpc_bid_micros": 1,
                  "customer_id": "string",
                  "keyword_id": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a keyword",
                    "properties": {
                      "ad_group_id": {
                        "description": "The ad group ID the keyword belongs to.",
                        "title": "Ad Group Id",
                        "type": "string"
                      },
                      "cpc_bid_micros": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New bid in micros (1 USD = 1,000,000 micros). Example: 2500000 for $2.50",
                        "title": "Cpc Bid Micros"
                      },
                      "customer_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Customer Id"
                      },
                      "keyword_id": {
                        "description": "The keyword ID to update. Use get_campaign_structure to find this.",
                        "title": "Keyword Id",
                        "type": "string"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: ENABLED or PAUSED",
                        "title": "Status"
                      }
                    },
                    "required": [
                      "keyword_id",
                      "ad_group_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_keyword)"
                  },
                  "success": true,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_keyword",
                  "is_error": true,
                  "success": false,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_keyword"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udea8 **IF THIS TOOL RETURNS A QUOTA ERROR:**\n- The error message will include a clickable upgrade link\n- Show the FULL e... [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_keyword"
      }
    },
    "/api/v1/tools/update_linkedin_campaign/execute": {
      "post": {
        "description": "User wants to modify LinkedIn campaign settings.\n\nUpdate campaign budget, schedule, targeting, or other settings.\n\nUpdatable Fields:\n- name: Campaign name\n- daily_budget: Daily budget in account currency (min 10)\n- total_budget: Lifetime budget\n- status: ACTIVE, PAUSED, or ARCHIVED\n- start_date / end_date: Schedule\n- locations, industries, seniorities, job_titles, company_sizes: Targeting\n\nParameters:\n- campaign_id: Campaign ID to update (required)\n- account_id: Optional LinkedIn Ad Account ID\n- [any updatable field]: New value\n\nExecution time: 2-3 seconds",
        "operationId": "execute_update_linkedin_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "daily_budget": 10.0,
                  "name": "string",
                  "start_date": "string",
                  "status": "string",
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a LinkedIn campaign",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to update",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New list of company size URNs",
                        "title": "Company Sizes"
                      },
                      "daily_budget": {
                        "anyOf": [
                          {
                            "minimum": 10.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget in account currency (minimum 10)",
                        "title": "Daily Budget"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New end date in ISO format",
                        "title": "End Date"
                      },
                      "industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New list of industry URNs",
                        "title": "Industries"
                      },
                      "job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New list of job title URNs",
                        "title": "Job Titles"
                      },
                      "locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New list of location URNs (replaces existing)",
                        "title": "Locations"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New campaign name",
                        "title": "Name"
                      },
                      "seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New list of seniority URNs",
                        "title": "Seniorities"
                      },
                      "start_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New start date in ISO format (e.g., '2025-01-15T00:00:00Z')",
                        "title": "Start Date"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: ACTIVE, PAUSED, or ARCHIVED",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New total/lifetime budget",
                        "title": "Total Budget"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_linkedin_campaign)"
                  },
                  "success": true,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_linkedin_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to modify LinkedIn campaign settings [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_linkedin_campaign"
      }
    },
    "/api/v1/tools/update_linkedin_campaign_budget/execute": {
      "post": {
        "description": "User wants to change a LinkedIn campaign's budget (daily or total).\n\nUpdate campaign daily budget, total/lifetime budget, or remove budget caps.\n\nParameters:\n- campaign_id: Campaign ID to update (required)\n- daily_budget: New daily budget in USD (minimum $10)\n- total_budget: New total/lifetime budget\n- remove_total_budget: Remove total budget cap (make unlimited)\n- account_id: Optional LinkedIn Ad Account ID\n\nUse this instead of `update_linkedin_campaign` when the user ONLY wants to change budget.\n\nExample Prompts:\n- \"Increase my LinkedIn campaign budget to $50/day\"\n- \"Set total budget to $5000 for campaign 12345\"\n- \"Change daily budget to $25\"\n- \"Remove the budget cap on my campaign\"\n- \"Double my LinkedIn ad spend\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_update_linkedin_campaign_budget",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "daily_budget": 10.0,
                  "remove_total_budget": false,
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating campaign budget",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to update",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "daily_budget": {
                        "anyOf": [
                          {
                            "minimum": 10.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget (minimum $10)",
                        "title": "Daily Budget"
                      },
                      "remove_total_budget": {
                        "default": false,
                        "description": "Remove total budget cap (make unlimited)",
                        "title": "Remove Total Budget",
                        "type": "boolean"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New total/lifetime budget",
                        "title": "Total Budget"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_linkedin_campaign_budget)"
                  },
                  "success": true,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_linkedin_campaign_budget",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_budget"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to change a LinkedIn campaign's budget (daily or total) [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_linkedin_campaign_budget"
      }
    },
    "/api/v1/tools/update_linkedin_campaign_group/execute": {
      "post": {
        "description": "User wants to modify a LinkedIn campaign group (rename, change status, update budget).\n\nUpdate campaign group name, status, total budget, or end date.\n\nParameters:\n- campaign_group_id: Campaign group ID to update (required)\n- name: New name for the group\n- status: New status (ACTIVE, PAUSED, or ARCHIVED)\n- total_budget: New total budget\n- end_date: New end date in ISO format\n- account_id: Optional LinkedIn Ad Account ID\n\nNote: Changes to a campaign group affect all campaigns within it.\n\nExample Prompts:\n- \"Rename my campaign group to Q2 2026\"\n- \"Pause campaign group 12345\"\n- \"Set the budget for my campaign group\"\n- \"Update my LinkedIn campaign group\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_update_linkedin_campaign_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_group_id": "string",
                  "end_date": "string",
                  "name": "string",
                  "status": "string",
                  "total_budget": 1.0
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a campaign group",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_group_id": {
                        "description": "Campaign group ID to update",
                        "title": "Campaign Group Id",
                        "type": "string"
                      },
                      "end_date": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New end date in ISO format",
                        "title": "End Date"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New name for the campaign group",
                        "title": "Name"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: ACTIVE, PAUSED, or ARCHIVED",
                        "title": "Status"
                      },
                      "total_budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New total budget",
                        "title": "Total Budget"
                      }
                    },
                    "required": [
                      "campaign_group_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_linkedin_campaign_group)"
                  },
                  "success": true,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_linkedin_campaign_group",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to modify a LinkedIn campaign group (rename, change status, update budget) [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_linkedin_campaign_group"
      }
    },
    "/api/v1/tools/update_linkedin_campaign_schedule/execute": {
      "post": {
        "description": "User wants to change a LinkedIn campaign's end date or schedule.\n\nUpdate the campaign end date.\n\nParameters:\n- campaign_id: Campaign ID to update (required)\n- end_date: New end date in ISO format, e.g. '2026-04-15T00:00:00Z' (required)\n- account_id: Optional LinkedIn Ad Account ID\n\nExample Prompts:\n- \"Extend my campaign until April 15\"\n- \"Change the end date of campaign 12345\"\n- \"Set my LinkedIn campaign to end next month\"\n- \"Push the campaign deadline to June\"\n\nExecution time: 2-3 seconds",
        "operationId": "execute_update_linkedin_campaign_schedule",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "campaign_id": "<campaign_id>",
                  "end_date": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating campaign schedule",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to update",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "end_date": {
                        "description": "New end date in ISO format (e.g. '2026-04-15T00:00:00Z')",
                        "title": "End Date",
                        "type": "string"
                      }
                    },
                    "required": [
                      "campaign_id",
                      "end_date"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_linkedin_campaign_schedule)"
                  },
                  "success": true,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_linkedin_campaign_schedule",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_schedule"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to change a LinkedIn campaign's end date or schedule [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_linkedin_campaign_schedule"
      }
    },
    "/api/v1/tools/update_linkedin_campaign_targeting/execute": {
      "post": {
        "description": "User wants to add or remove targeting criteria from a LinkedIn campaign.\n\nSmart targeting update - incrementally add or remove targeting facets without replacing everything.\n\nParameters:\n- campaign_id: Campaign ID to update targeting for (required)\n- add_locations / remove_locations: Location URNs to add/remove\n- add_industries / remove_industries: Industry URNs to add/remove\n- add_seniorities / remove_seniorities: Seniority URNs to add/remove\n- add_job_titles / remove_job_titles: Job title URNs to add/remove\n- add_company_sizes / remove_company_sizes: Company size URNs to add/remove\n- replace_all: If True, replaces ALL targeting (default: False, incremental)\n- account_id: Optional LinkedIn Ad Account ID\n\nUse `search_linkedin_targeting` first to find the correct URNs for targeting criteria.\n\nExample Prompts:\n- \"Add New York to my campaign targeting\"\n- \"Remove the finance industry from targeting\"\n- \"Target VP and C-suite in my campaign\"\n- \"Change targeting to include tech companies\"\n- \"Add software engineers to my ad targeting\"\n\nExecution time: 3-5 seconds",
        "operationId": "execute_update_linkedin_campaign_targeting",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "add_industries": [
                    "string"
                  ],
                  "add_locations": [
                    "string"
                  ],
                  "add_seniorities": [
                    "string"
                  ],
                  "campaign_id": "<campaign_id>",
                  "remove_industries": [
                    "string"
                  ],
                  "remove_locations": [
                    "string"
                  ],
                  "remove_seniorities": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for smart targeting update",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "add_age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Age range URNs to ADD (include-only)",
                        "title": "Add Age Ranges"
                      },
                      "add_buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Buyer group URNs to ADD (API 2026-03+)",
                        "title": "Add Buyer Groups"
                      },
                      "add_company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Company size URNs to ADD",
                        "title": "Add Company Sizes"
                      },
                      "add_degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Degree URNs to ADD",
                        "title": "Add Degrees"
                      },
                      "add_employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Employer URNs to ADD",
                        "title": "Add Employers"
                      },
                      "add_fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Field of study URNs to ADD",
                        "title": "Add Fields Of Study"
                      },
                      "add_followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Followed company URNs to ADD",
                        "title": "Add Followed Companies"
                      },
                      "add_genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Gender URNs to ADD (include-only)",
                        "title": "Add Genders"
                      },
                      "add_industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Industry URNs to ADD",
                        "title": "Add Industries"
                      },
                      "add_interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Interest URNs to ADD",
                        "title": "Add Interests"
                      },
                      "add_job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Job function URNs to ADD",
                        "title": "Add Job Functions"
                      },
                      "add_job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Job title URNs to ADD",
                        "title": "Add Job Titles"
                      },
                      "add_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Location URNs to ADD to targeting",
                        "title": "Add Locations"
                      },
                      "add_member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Member behavior URNs to ADD",
                        "title": "Add Member Behaviors"
                      },
                      "add_member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn group URNs to ADD (include-only)",
                        "title": "Add Member Groups"
                      },
                      "add_schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "School URNs to ADD",
                        "title": "Add Schools"
                      },
                      "add_seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Seniority URNs to ADD",
                        "title": "Add Seniorities"
                      },
                      "add_skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Skill URNs to ADD",
                        "title": "Add Skills"
                      },
                      "add_years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Years of experience URNs to ADD",
                        "title": "Add Years Of Experience"
                      },
                      "campaign_id": {
                        "description": "Campaign ID to update targeting for",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "remove_age_ranges": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Age range URNs to REMOVE",
                        "title": "Remove Age Ranges"
                      },
                      "remove_buyer_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Buyer group URNs to REMOVE",
                        "title": "Remove Buyer Groups"
                      },
                      "remove_company_sizes": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Company size URNs to REMOVE",
                        "title": "Remove Company Sizes"
                      },
                      "remove_degrees": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Degree URNs to REMOVE",
                        "title": "Remove Degrees"
                      },
                      "remove_employers": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Employer URNs to REMOVE",
                        "title": "Remove Employers"
                      },
                      "remove_fields_of_study": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Field of study URNs to REMOVE",
                        "title": "Remove Fields Of Study"
                      },
                      "remove_followed_companies": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Followed company URNs to REMOVE",
                        "title": "Remove Followed Companies"
                      },
                      "remove_genders": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Gender URNs to REMOVE",
                        "title": "Remove Genders"
                      },
                      "remove_industries": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Industry URNs to REMOVE",
                        "title": "Remove Industries"
                      },
                      "remove_interests": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Interest URNs to REMOVE",
                        "title": "Remove Interests"
                      },
                      "remove_job_functions": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Job function URNs to REMOVE",
                        "title": "Remove Job Functions"
                      },
                      "remove_job_titles": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Job title URNs to REMOVE",
                        "title": "Remove Job Titles"
                      },
                      "remove_locations": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Location URNs to REMOVE from targeting",
                        "title": "Remove Locations"
                      },
                      "remove_member_behaviors": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Member behavior URNs to REMOVE",
                        "title": "Remove Member Behaviors"
                      },
                      "remove_member_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn group URNs to REMOVE",
                        "title": "Remove Member Groups"
                      },
                      "remove_schools": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "School URNs to REMOVE",
                        "title": "Remove Schools"
                      },
                      "remove_seniorities": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Seniority URNs to REMOVE",
                        "title": "Remove Seniorities"
                      },
                      "remove_skills": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Skill URNs to REMOVE",
                        "title": "Remove Skills"
                      },
                      "remove_years_of_experience": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Years of experience URNs to REMOVE",
                        "title": "Remove Years Of Experience"
                      },
                      "replace_all": {
                        "default": false,
                        "description": "If True, replaces ALL targeting. If False, adds/removes incrementally.",
                        "title": "Replace All",
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_linkedin_campaign_targeting)"
                  },
                  "success": true,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_linkedin_campaign_targeting",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_campaign_targeting"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to add or remove targeting criteria from a LinkedIn campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_linkedin_campaign_targeting"
      }
    },
    "/api/v1/tools/update_linkedin_creative/execute": {
      "post": {
        "description": "User wants to edit a LinkedIn ad/creative.\n\nUpdate ad copy, headline, CTA, or status.\n\nUpdatable Fields:\n- status: ACTIVE or PAUSED\n- introductory_text: Main ad copy (max 600 chars)\n- headline: Ad headline (max 70 chars)\n- call_to_action: CTA button label\n- landing_page_url: Destination URL\n\nNote: LinkedIn may re-review the creative after content changes.\n\nExecution time: 2-3 seconds",
        "operationId": "execute_update_linkedin_creative",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "call_to_action": "string",
                  "campaign_id": "<campaign_id>",
                  "creative_id": "string",
                  "headline": "string",
                  "introductory_text": "string",
                  "name": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a LinkedIn creative",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Account Id"
                      },
                      "call_to_action": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New CTA label",
                        "title": "Call To Action"
                      },
                      "campaign_id": {
                        "description": "Parent campaign ID",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "creative_id": {
                        "description": "Creative ID to update",
                        "title": "Creative Id",
                        "type": "string"
                      },
                      "headline": {
                        "anyOf": [
                          {
                            "maxLength": 70,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New headline (max 70 characters). Note: LinkedIn may NOT allow updating inline content on non-DRAFT creatives.",
                        "title": "Headline"
                      },
                      "introductory_text": {
                        "anyOf": [
                          {
                            "maxLength": 600,
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New ad copy (max 600 characters). Note: LinkedIn may NOT allow updating inline content on non-DRAFT creatives.",
                        "title": "Introductory Text"
                      },
                      "landing_page_url": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New destination URL",
                        "title": "Landing Page Url"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New creative name (shown in Campaign Manager UI). Per LinkedIn API 2026-03, name is updatable via PARTIAL_UPDATE.",
                        "title": "Name"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: ACTIVE or PAUSED",
                        "title": "Status"
                      }
                    },
                    "required": [
                      "creative_id",
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_linkedin_creative)"
                  },
                  "success": true,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_linkedin_creative",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_linkedin_creative"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to edit a LinkedIn ad/creative [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_linkedin_creative"
      }
    },
    "/api/v1/tools/update_meta_ad/execute": {
      "post": {
        "description": "User wants to update an individual Meta ad \u2014 pause/resume it, rename it, or swap its creative.\n\nIMPORTANT: This tool modifies REAL ads in Meta Ads Manager. Changes take effect immediately.\n\nReturns:\n- Confirmation of updates applied\n- Summary of changes\n- Ads Manager URL for the ad\n\nWhen to use this tool:\n- \"Pause this ad\"\n- \"Resume ad [ID]\"\n- \"Rename this ad\"\n- \"Swap the creative on this ad\"\n- \"Change the image on this ad\" (via creative swap)\n- \"Turn off the underperforming ad\"\n\nParameters:\n- ad_id: The Meta Ad ID to update (required)\n- status: ACTIVE, PAUSED, DELETED, ARCHIVED (optional)\n- name: New ad name (optional)\n- creative_id: New creative ID for creative swap (optional)\n\nAt least one update field must be provided.\n\nExecution time: 2-5 seconds\nModifies: Real ad in Meta Ads\n\nWorkflow for creative swap:\n1. Use `discover_meta_assets` to find existing images/creatives\n2. Get the creative ID you want to use\n3. Use `update_meta_ad` with `creative_id` to swap\n\nWorkflow for pausing underperformers:\n1. Use `analyze_meta_ad_performance` to identify underperforming ads\n2. Use `list_meta_ads` to get ad IDs\n3. Use `update_meta_ad` with `status=PAUSED`",
        "operationId": "execute_update_meta_ad",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "ad_id": "string",
                  "creative_id": "string",
                  "name": "string",
                  "status": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a Meta ad.\n\nSupported fields: status, name, creative_id.\n\nNOT supported here: primary_text, headline, description, call_to_action_type.\nMeta does not allow editing creative text fields in-place. To change ad copy or CTA,\ncreate a new ad with the updated text using add_meta_ad, then pause the old ad.",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "ad_id": {
                        "description": "The Meta Ad ID to update (required)",
                        "title": "Ad Id",
                        "type": "string"
                      },
                      "creative_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New creative ID for creative swap. Use this to replace the ad's creative with an existing creative.",
                        "title": "Creative Id"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New ad name",
                        "title": "Name"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: 'ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED'",
                        "title": "Status"
                      }
                    },
                    "required": [
                      "ad_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_meta_ad)"
                  },
                  "success": true,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_meta_ad",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to update an individual Meta ad \u2014 pause/resume it, rename it, or swap its creative [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_meta_ad"
      }
    },
    "/api/v1/tools/update_meta_ad_set/execute": {
      "post": {
        "description": "User wants to edit an existing Meta ad set's targeting, budget, bid, placements, schedule, or optimization settings.\n\nIMPORTANT: This tool modifies REAL ad sets in Meta Ads Manager. Changes take effect immediately.\n\nThis is the primary tool for:\n- Changing audience targeting (age, gender, interests, locations)\n- Excluding placements (e.g., remove Audience Network)\n- Adjusting budgets at the ad set level\n- Changing bid amounts\n- Pausing/resuming specific ad sets\n- Modifying optimization goals\n\nReturns:\n- Confirmation of updates applied\n- Summary of all changes made\n- Ads Manager URL for the ad set\n\nWhen to use this tool:\n- \"Change the targeting on my ad set\"\n- \"Exclude Audience Network from placements\"\n- \"Update the budget on this ad set to $50/day\"\n- \"Pause this ad set\"\n- \"Change the age range to 25-45\"\n- \"Add interest targeting for fitness\"\n- \"Exclude custom audience from this ad set\"\n- \"Change bid to $5\"\n- \"Update the optimization goal\"\n\nParameters:\n- ad_set_id: The Meta Ad Set ID to update (required)\n- status: ACTIVE, PAUSED, DELETED, ARCHIVED (optional)\n- name: New ad set name (optional)\n- daily_budget: New daily budget in USD (optional, min $1). DO NOT use for CBO campaigns.\n- lifetime_budget: New lifetime budget in USD (optional). DO NOT use for CBO campaigns.\n- daily_min_spend_target: CBO only \u2014 minimum daily spend for this ad set (use INSTEAD of daily_budget)\n- daily_spend_cap: CBO only \u2014 maximum daily spend cap for this ad set (use INSTEAD of daily_budget)\n- lifetime_min_spend_target: CBO only \u2014 minimum lifetime spend (for lifetime budget CBO)\n- lifetime_spend_cap: CBO only \u2014 maximum lifetime spend cap (for lifetime budget CBO)\n- bid_amount: New bid amount in USD (optional)\n- targeting: New targeting spec as JSON (optional) \u2014 for placements, audiences, demographics\n- start_time: New start time ISO format (optional)\n- end_time: New end time ISO format (optional)\n- optimization_goal: REACH, LINK_CLICKS, LANDING_PAGE_VIEWS, OFFSITE_CONVERSIONS, VALUE, etc. (optional)\n\n**CBO (Advantage Campaign Budget) campaigns:**\nFor ad sets under CBO campaigns, do NOT set daily_budget or lifetime_budget.\nUse daily_min_spend_target / daily_spend_cap to control spend distribution.\nSetting daily_budget on a CBO ad set will cause Meta to reject with an error.\n\nAt least one update field must be provided.\n\nTargeting Spec Examples:\n\n*Exclude Audience Network:*\n```json\n{\n  \"publisher_platforms\": [\"facebook\", \"instagram\"],\n  \"facebook_positions\": [\"feed\", \"stories\", \"reels\"],\n  \"instagram_positions\": [\"stream\", \"story\", \"reels\"]\n}\n```\n\n*Change age and gender:*\n```json\n{\n  \"age_min\": 25,\n  \"age_max\": 45,\n  \"genders\": [1]\n}\n```\n(genders: 1=male, 2=female, omit for all)\n\n*Add interest targeting:*\n```json\n{\n  \"flexible_spec\": [{\"interests\": [{\"id\": \"6003139266461\", \"name\": \"Fitness\"}]}]\n}\n```\n\n*Exclude custom audience:*\n```json\n{\n  \"excluded_custom_audiences\": [{\"id\": \"AUDIENCE_ID\"}]\n}\n```\n\nExecution time: 2-5 seconds\nModifies: Real ad set in Meta Ads\n\nWorkflow:\n1. Use `list_meta_ad_sets` or `get_meta_campaign_details` to find the ad set ID\n2. Use `update_meta_ad_set` with the changes you want to make\n3. Verify changes in Ads Manager",
        "operationId": "execute_update_meta_ad_set",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_set_id": "string",
                  "bid_amount": 0.01,
                  "daily_budget": 1.0,
                  "lifetime_budget": 1.0,
                  "name": "string",
                  "status": "string",
                  "targeting": {
                    "age_max": 55,
                    "age_min": 25,
                    "genders": [
                      1,
                      2
                    ],
                    "geo_locations": {
                      "countries": [
                        "US"
                      ]
                    },
                    "interests": [
                      {
                        "id": "6003107902433",
                        "name": "Fitness and wellness"
                      }
                    ]
                  }
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a Meta ad set",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "ad_set_id": {
                        "description": "The Meta Ad Set ID to update (required)",
                        "title": "Ad Set Id",
                        "type": "string"
                      },
                      "bid_amount": {
                        "anyOf": [
                          {
                            "minimum": 0.01,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New bid amount in USD. Will be converted to cents for Meta API.",
                        "title": "Bid Amount"
                      },
                      "daily_budget": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget in USD (minimum $1). Will be converted to cents for Meta API.",
                        "title": "Daily Budget"
                      },
                      "daily_min_spend_target": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Min Spend Target"
                      },
                      "daily_spend_cap": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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.",
                        "title": "Daily Spend Cap"
                      },
                      "end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New end time in ISO format YYYY-MM-DDTHH:MM:SS",
                        "title": "End Time"
                      },
                      "lifetime_budget": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New lifetime budget in USD. Will be converted to cents for Meta API.",
                        "title": "Lifetime Budget"
                      },
                      "lifetime_min_spend_target": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Minimum lifetime spend target in account currency for CBO campaigns with lifetime budget.",
                        "title": "Lifetime Min Spend Target"
                      },
                      "lifetime_spend_cap": {
                        "anyOf": [
                          {
                            "minimum": 0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Maximum lifetime spend cap in account currency for CBO campaigns with lifetime budget.",
                        "title": "Lifetime Spend Cap"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New ad set name",
                        "title": "Name"
                      },
                      "optimization_goal": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New optimization goal: REACH, IMPRESSIONS, LINK_CLICKS, LANDING_PAGE_VIEWS, OFFSITE_CONVERSIONS, VALUE, etc.",
                        "title": "Optimization Goal"
                      },
                      "start_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New start time in ISO format YYYY-MM-DDTHH:MM:SS",
                        "title": "Start Time"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: 'ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED'",
                        "title": "Status"
                      },
                      "targeting": {
                        "anyOf": [
                          {
                            "additionalProperties": true,
                            "type": "object"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New targeting specification as a JSON object. Use this for placement exclusions, audience exclusions, age/gender changes, interest targeting updates, and location targeting changes.",
                        "title": "Targeting"
                      }
                    },
                    "required": [
                      "ad_set_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_meta_ad_set)"
                  },
                  "success": true,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_meta_ad_set",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_ad_set"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to edit an existing Meta ad set's targeting, budget, bid, placements, schedule, or optimization settings [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_meta_ad_set"
      }
    },
    "/api/v1/tools/update_meta_campaign/execute": {
      "post": {
        "description": "User wants to update an existing Meta campaign's status, budget, name, or schedule.\n\nIMPORTANT: This tool modifies REAL campaigns in Meta Ads Manager. Changes take effect immediately.\n\nReturns:\n- Confirmation of updates applied\n- Updated campaign details\n- Ads Manager URL\n\nWhen to use this tool:\n- \"Update my Meta campaign budget\"\n- \"Change my Facebook campaign name\"\n- \"Pause my Instagram campaign\"\n- \"Resume my paused campaign\"\n- \"Set a new budget for campaign X\"\n- \"Change the end date for my campaign\"\n\nParameters:\n- campaign_id: The Meta Campaign ID to update (required)\n- status: New status - 'ACTIVE', 'PAUSED' (optional)\n- name: New campaign name (optional)\n- daily_budget: New daily budget in USD (optional)\n- lifetime_budget: New lifetime budget in USD (optional)\n- start_time: New start time in ISO format (optional)\n- stop_time: New stop time in ISO format (optional)\n\nAt least one update field must be provided.\n\nExecution time: 2-5 seconds\nModifies: Real campaign in Meta Ads",
        "operationId": "execute_update_meta_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "campaign_id": "<campaign_id>",
                  "daily_budget": 1.0,
                  "lifetime_budget": 1.0,
                  "name": "string",
                  "start_time": "string",
                  "status": "string",
                  "stop_time": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for updating a Meta campaign",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Ad Account Id"
                      },
                      "campaign_id": {
                        "description": "The Meta Campaign ID to update (required)",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "daily_budget": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget in USD (optional, minimum $1)",
                        "title": "Daily Budget"
                      },
                      "lifetime_budget": {
                        "anyOf": [
                          {
                            "minimum": 1.0,
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New lifetime budget in USD (optional)",
                        "title": "Lifetime Budget"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New campaign name (optional)",
                        "title": "Name"
                      },
                      "start_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New start time in ISO format YYYY-MM-DDTHH:MM:SS (optional)",
                        "title": "Start Time"
                      },
                      "status": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New status: 'ACTIVE', 'PAUSED' (optional)",
                        "title": "Status"
                      },
                      "stop_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New stop time in ISO format YYYY-MM-DDTHH:MM:SS (optional)",
                        "title": "Stop Time"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_meta_campaign)"
                  },
                  "success": true,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_meta_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_meta_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to update an existing Meta campaign's status, budget, name, or schedule [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_meta_campaign"
      }
    },
    "/api/v1/tools/update_tiktok_ad_group/execute": {
      "post": {
        "description": "Update TikTok ad group settings: name, budget, targeting (age, gender, locations), schedule.",
        "operationId": "execute_update_tiktok_ad_group",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "adgroup_id": "string",
                  "age_groups": [
                    "string"
                  ],
                  "budget": 1.0,
                  "gender": "string",
                  "location_ids": [
                    "string"
                  ],
                  "name": "string",
                  "schedule_end_time": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for updating TikTok ad group fields",
                    "properties": {
                      "adgroup_id": {
                        "description": "TikTok ad group ID",
                        "title": "Adgroup Id",
                        "type": "string"
                      },
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "age_groups": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Age groups: AGE_13_17, AGE_18_24, AGE_25_34, AGE_35_44, AGE_45_54, AGE_55_100",
                        "title": "Age Groups"
                      },
                      "budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New budget in account currency",
                        "title": "Budget"
                      },
                      "gender": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "GENDER_MALE, GENDER_FEMALE, or GENDER_UNLIMITED",
                        "title": "Gender"
                      },
                      "location_ids": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok location IDs",
                        "title": "Location Ids"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New ad group name",
                        "title": "Name"
                      },
                      "schedule_end_time": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "End time: YYYY-MM-DD HH:MM:SS",
                        "title": "Schedule End Time"
                      }
                    },
                    "required": [
                      "adgroup_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_tiktok_ad_group)"
                  },
                  "success": true,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_tiktok_ad_group",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_ad_group"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update TikTok ad group settings: name, budget, targeting (age, gender, locations), schedule [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_tiktok_ad_group"
      }
    },
    "/api/v1/tools/update_tiktok_campaign/execute": {
      "post": {
        "description": "Update TikTok campaign settings like name, budget, or budget mode.",
        "operationId": "execute_update_tiktok_campaign",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "budget": 1.0,
                  "budget_mode": "string",
                  "campaign_id": "<campaign_id>",
                  "name": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for updating TikTok campaign fields",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "budget": {
                        "anyOf": [
                          {
                            "type": "number"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New daily budget in account currency",
                        "title": "Budget"
                      },
                      "budget_mode": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "BUDGET_MODE_DAY or BUDGET_MODE_TOTAL",
                        "title": "Budget Mode"
                      },
                      "campaign_id": {
                        "description": "TikTok campaign ID",
                        "title": "Campaign Id",
                        "type": "string"
                      },
                      "name": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "New campaign name",
                        "title": "Name"
                      }
                    },
                    "required": [
                      "campaign_id"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for update_tiktok_campaign)"
                  },
                  "success": true,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: update_tiktok_campaign",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "update_tiktok_campaign"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Update TikTok campaign settings like name, budget, or budget mode [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "update_tiktok_campaign"
      }
    },
    "/api/v1/tools/upload_tiktok_images/execute": {
      "post": {
        "description": "Upload images to TikTok Asset Library from public URLs.\n\nReturns TikTok image_ids that can be used for:\n- create_tiktok_carousel_card \u2014 build carousel cards from uploaded images\n- create_tiktok_campaign with existing_image_ids \u2014 standard image campaigns\n- add_tiktok_ad with image_ids \u2014 add image ads to existing ad groups\n\nTikTok downloads images directly from the provided URLs (no intermediate storage needed).\n\nSupported formats: JPG, PNG, WEBP. Max 10MB per image. Max 20 images per call.\n\nUse this tool when the user provides image URLs and you need TikTok image IDs.",
        "operationId": "execute_upload_tiktok_images",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "image_urls": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input for uploading images to TikTok Asset Library",
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID (optional).",
                        "title": "Advertiser Id"
                      },
                      "image_urls": {
                        "description": "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.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Image Urls",
                        "type": "array"
                      }
                    },
                    "required": [
                      "image_urls"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for upload_tiktok_images)"
                  },
                  "success": true,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: upload_tiktok_images",
                  "is_error": true,
                  "success": false,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "upload_tiktok_images"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Upload images to TikTok Asset Library from public URLs [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "upload_tiktok_images"
      }
    },
    "/api/v1/tools/validate_and_prepare_assets/execute": {
      "post": {
        "description": "\ud83d\udd04 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 \u2192 validate \u2192 commit.\n\n\u26a0\ufe0f CRITICAL: This tool accepts IMAGE URLS (from postimages.org), NOT base64 or file paths!\n\n\ud83d\udccb **YOUR CRITICAL ROLE: URL Validator & Categorizer**\n\n**STEP 1: Call help_user_upload First**\n\nBefore anything else:\n- Call help_user_upload() to show upload instructions\n- User will upload to postimages.org and paste Direct links\n- User provides links like: https://i.postimg.cc/ABC123/image.jpg\n\n**STEP 2: Categorize URLs by Examining Images**\n\nWhen user pastes URLs, YOU MUST:\n1. **Request to view each image** (you can see images from URLs)\n2. **Calculate aspect ratio** for each: width / height\n3. **Categorize based on ratio**:\n   - Landscape (1.8-2.0): ~1.91:1 like 1200\u00d7628px\n   - Square (0.95-1.05): ~1:1 like 1200\u00d71200px\n   - Portrait (0.75-0.85): ~0.8:1 like 960\u00d71200px\n   - Logo: Small images or very wide (4:1)\n4. **Verify minimum requirements**:\n   - At least 1 landscape\n   - At least 1 square (non-logo)\n   - At least 1 logo\n5. If missing types, ask user to upload more\n\n2. **Tell them what images they need:**\n   - **REQUIRED:**\n     - At least 1 landscape image (1.91:1 ratio, like 1200\u00d7628px or 600\u00d7314px)\n     - At least 1 square image (1:1 ratio, like 1200\u00d71200px or 300\u00d7300px)\n     - At least 1 square logo (1:1 ratio, min 128\u00d7128px, recommended 1200\u00d71200px)\n   - **OPTIONAL:**\n     - Portrait images (4:5 ratio, like 960\u00d71200px or 400\u00d7600px)\n     - Landscape logos (4:1 ratio, like 1200\u00d7300px or 512\u00d7128px)\n\n3. **Provide OS-specific help if user asks how to check/resize images:**\n   - **Mac users:**\n     - \"Right-click image \u2192 Get Info \u2192 shows dimensions\"\n     - \"Use Preview app \u2192 Tools \u2192 Adjust Size to resize\"\n     - \"Recommended free tool: GIMP (gimp.org) for precise aspect ratio control\"\n\n   - **Windows users:**\n     - \"Right-click image \u2192 Properties \u2192 Details tab \u2192 shows dimensions\"\n     - \"Use Paint \u2192 Resize \u2192 maintain aspect ratio checkbox\"\n     - \"Recommended free tool: GIMP (gimp.org) or Paint.NET\"\n\n   - **Online tools (any OS):**\n     - \"Canva.com (free, easy templates for exact sizes)\"\n     - \"Photopea.com (free Photoshop alternative)\"\n     - \"ResizePixel.com (quick resize tool)\"\n\n4. **CRITICAL: Read, Examine, and Categorize Images BEFORE Calling This Tool:**\n\n   **Step 4a: READ FILE CONTENTS (REQUIRED!)**\n   - When user uploads images, ChatGPT stores them as files with paths like `/mnt/data/image.png`\n   - **YOU MUST READ EACH FILE** to get the actual image data\n   - **DO NOT** send file paths or FILE:file_xxx references to this tool\n   - **DO NOT** send data URIs with FILE: references like `data:image/png;base64,FILE:file_xxx`\n   - **YOU MUST** read the file contents using your file reading capabilities\n   - Convert file contents to base64 string\n   - Send actual base64 encoded data (the binary image bytes encoded as base64)\n\n   **Step 4b: EXAMINE AND CATEGORIZE**\n   - **USE EXTENDED THINKING** to analyze each image\n   - When you read the image file, you can see its dimensions\n   - Calculate aspect ratio: width / height\n   - Categorize based on aspect ratio:\n     * **Landscape** (1.80-2.00 ratio): ~1.91:1 images like 1200\u00d7628px\n     * **Square** (0.95-1.05 ratio): ~1:1 images like 1200\u00d71200px\n     * **Portrait** (0.75-0.85 ratio): ~0.8:1 (4:5) images like 960\u00d71200px\n     * **Logo Square** (0.95-1.05 ratio + small size <500px): Small square logos\n     * **Logo Landscape** (3.80-4.20 ratio): ~4:1 images like 1200\u00d7300px\n\n   **Step 4c: VERIFY MINIMUM REQUIREMENTS**\n   - Count images in each category\n   - **REQUIRED MINIMUM**:\n     * At least 1 landscape image\n     * At least 1 square image (non-logo)\n     * At least 1 logo (square or landscape)\n   - If missing ANY required type:\n     * Tell user: \"You need at least 1 landscape, 1 square, and 1 logo image\"\n     * Ask user to upload missing types\n     * DO NOT call this tool yet\n\n**STEP 2: Call This Tool with ACTUAL Base64 Data**\n\n\u26a0\ufe0f **CRITICAL - Data Format**:\n```python\n# \u274c WRONG - DO NOT SEND:\n\"data:image/png;base64,FILE:file_xxx\"  # File reference - will fail!\n\"/mnt/data/image.png\"  # File path - will fail!\n\n# \u2705 CORRECT - SEND THIS:\n\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...\"  # Actual base64 encoded image bytes\n# OR just the base64 string without prefix:\n\"iVBORw0KGgoAAAANSUhEUgAA...\"  # Also works\n```\n\n**How to get actual base64**:\n1. Read the uploaded file from its path\n2. Get the file contents (binary data)\n3. Encode to base64 string\n4. Pass to this tool\n\n**Example**:\n- User uploads `image.png`\n- ChatGPT stores as `/mnt/data/image123.png`\n- You read the file \u2192 get binary PNG data\n- You encode to base64 \u2192 get \"iVBORw0K...\"\n- You send to tool: `marketing_images_landscape: [\"iVBORw0K...\"]`\n\n**IMPORTANT**:\n- The tool needs ACTUAL image data to validate dimensions and aspect ratios\n- File references cannot be validated\n- You must convert files to base64 before calling this tool\n\n**Image Requirements (Google Ads PMax specs):**\n\n| Type | Aspect Ratio | Min Size | Recommended | Max File Size |\n|------|--------------|----------|-------------|---------------|\n| Landscape | 1.91:1 | 600\u00d7314px | 1200\u00d7628px | 5MB |\n| Square | 1:1 | 300\u00d7300px | 1200\u00d71200px | 5MB |\n| Portrait | 4:5 (0.8:1) | 400\u00d7600px | 960\u00d71200px | 5MB |\n| Logo Square | 1:1 | 128\u00d7128px | 1200\u00d71200px | 5MB |\n| Logo Landscape | 4:1 | 512\u00d7128px | 1200\u00d7300px | 5MB |\n\n- **Tolerance:** \u00b12% aspect ratio deviation allowed\n- **Formats:** JPEG, PNG, WEBP only\n\n**STEP 3: Pre-Flight Checks (BEFORE Calling Tool)**\n\n**Check #1: Count Images**\n- Count how many images you categorized in each type\n- **REQUIRED MINIMUM**:\n  * 1+ landscape images\n  * 1+ square images (non-logo)\n  * 1+ logo images\n- If missing ANY required type:\n  * \"I see you uploaded [N] images, but I need at least 1 landscape, 1 square, and 1 logo.\"\n  * \"Please upload [missing type] image(s).\"\n  * DO NOT call this tool yet\n  * Wait for user to upload more images\n\n**Check #2: Verify Categorization**\n- Use extended thinking to double-check your categorization\n- Example thinking:\n  * \"Image 1: 1200\u00d7628 = 1.91 ratio \u2192 landscape \u2713\"\n  * \"Image 2: 1200\u00d71200 = 1.0 ratio, large size \u2192 square marketing image \u2713\"\n  * \"Image 3: 200\u00d7200 = 1.0 ratio, small size \u2192 logo \u2713\"\n- If uncertain about categorization, explain to user and ask for clarification\n\n**STEP 4: Handle Validation Results**\n\n**If validation FAILS:**\n- Show the user the EXACT error message from this tool\n- Explain what the error means in simple terms\n- Provide step-by-step instructions to fix:\n  - How to check current dimensions\n  - What the correct dimensions should be\n  - How to resize the image\n  - How to re-upload\n\n**If validation SUCCEEDS:**\n- Celebrate! \u2705\n- Show the asset_bundle_id to the user\n- Tell them the bundle is valid for 1 hour\n- Move to collecting campaign text details (name, headlines, etc.)\n- DO NOT call create_pmax_campaign until you have ALL text details\n\n**Returns:**\n- SUCCESS: asset_bundle_id (UUID) + summary of validated images\n- FAILURE: Specific error for the first invalid image + instructions to fix\n\n**Common Validation Errors & How to Help:**\n\n1. **Wrong aspect ratio**\n   - Error: \"Expected 1.91:1, got 2.0:1\"\n   - Help: \"Your image is slightly too wide. Crop it to 1200\u00d7628px exactly.\"\n\n2. **Image too small**\n   - Error: \"Minimum size is 600\u00d7314px, got 500\u00d7250px\"\n   - Help: \"Use a higher resolution image or upscale this one.\"\n\n3. **File too large**\n   - Error: \"File too large. Maximum 5 MB, got 6.2 MB\"\n   - Help: \"Compress the image using an online tool or save as JPEG with lower quality.\"\n\n4. **Wrong format**\n   - Error: \"Unsupported format 'BMP'\"\n   - Help: \"Convert to JPEG or PNG format.\"\n\n**Example User Flow:**\n\nUser: \"I want to create a PMax campaign\"\nYou: \"Great! Let's start by uploading your images. You'll need:\n- At least 1 landscape image (1200\u00d7628px recommended)\n- At least 1 square image (1200\u00d71200px recommended)\n- At least 1 square logo (1200\u00d71200px recommended)\n\nClick the paperclip icon (\ud83d\udcce) to attach your images.\"\n\n[User uploads images]\n\nYou: \"Perfect! I can see your images. Let me validate them...\"\n[Call validate_and_prepare_assets]\n\n**Execution Time:** 1-3 seconds (fast validation)\n\n**Authentication:** NOT required for this tool (validation only)\n\n**REMEMBER:**\n- Be extremely patient and helpful with image validation\n- Provide clear, actionable instructions for fixing errors\n- Never proceed to create_pmax_campaign without a valid asset_bundle_id\n- The bundle expires in 1 hour - if expired, re-validate images",
        "operationId": "execute_validate_and_prepare_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "logos_landscape": [
                    "string"
                  ],
                  "logos_square": [
                    "string"
                  ],
                  "marketing_images_landscape": [
                    "string"
                  ],
                  "marketing_images_portrait": [
                    "string"
                  ],
                  "marketing_images_square": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for validate_and_prepare_assets tool - NOW ACCEPTS URLs!",
                    "properties": {
                      "logos_landscape": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Landscape logo URLs (4:1 ratio, min 512\u00d7128px). Direct HTTPS links. Optional.",
                        "title": "Logos Landscape"
                      },
                      "logos_square": {
                        "description": "Square logo URLs (1:1 ratio, min 128\u00d7128px). Direct HTTPS links. Minimum 1 required.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Logos Square",
                        "type": "array"
                      },
                      "marketing_images_landscape": {
                        "description": "Landscape image URLs (1.91:1 ratio, min 600\u00d7314px). Direct HTTPS links from postimages.org ending in .jpg/.png/.webp. Minimum 1 required.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Marketing Images Landscape",
                        "type": "array"
                      },
                      "marketing_images_portrait": {
                        "anyOf": [
                          {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Portrait image URLs (4:5 ratio = 0.8:1, min 400\u00d7600px). Direct HTTPS links. Optional.",
                        "title": "Marketing Images Portrait"
                      },
                      "marketing_images_square": {
                        "description": "Square image URLs (1:1 ratio, min 300\u00d7300px). Direct HTTPS links ending in .jpg/.png/.webp. Minimum 1 required.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Marketing Images Square",
                        "type": "array"
                      }
                    },
                    "required": [
                      "marketing_images_landscape",
                      "marketing_images_square",
                      "logos_square"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for validate_and_prepare_assets)"
                  },
                  "success": true,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: validate_and_prepare_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "\ud83d\udd04 LONG-RUNNING TOOL: Validates multiple images from URLs for Performance Max campaigns [write]",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "validate_and_prepare_assets"
      }
    },
    "/api/v1/tools/validate_and_prepare_linkedin_assets/execute": {
      "post": {
        "description": "User provides image URLs to validate BEFORE creating LinkedIn image campaign.\n\nValidate and prepare new image assets for LinkedIn ad campaigns.\n\nIMPORTANT:\n- Call `get_linkedin_organizations` FIRST to get organization_id AND account_id\n- Include `account_id` when calling this tool - it's REQUIRED for images to be discoverable later!\n\nLinkedIn Image Requirements:\n- Horizontal (Sponsored Content): 1200x627 pixels\n- Square (Sponsored Content): 1080x1080 pixels\n- Vertical (Stories/Mobile): 1200x1500 pixels\n- Carousel cards: 1080x1080 pixels (each card)\n- Maximum file size: 8MB\n- Formats: JPEG, PNG, GIF (non-animated)\n\nParameters:\n- image_urls: List of public image URLs (e.g., from postimages.org, imgbb.com)\n- organization_id: LinkedIn Organization (Company Page) ID\n- account_id: LinkedIn Ad Account ID - REQUIRED for images to be discoverable via `discover_linkedin_assets`!\n- ad_type: Target ad type (single_image_horizontal, single_image_square, single_image_vertical, carousel)\n\nReturns:\n- Validation results for each image\n- asset_bundle_id (valid for 60 minutes)\n- Image URNs for reference\n\nExecution time: 5-15 seconds (downloads, validates, and uploads images)\n\nWorkflow:\n1. Call `get_linkedin_organizations` - get organization_id AND account_id\n2. Call `validate_and_prepare_linkedin_assets` with BOTH organization_id AND account_id\n3. Use returned `asset_bundle_id` in `create_linkedin_image_campaign`\n\nWhy account_id matters:\n- Without account_id, images are uploaded but NOT associated with your ad account\n- This means `discover_linkedin_assets` won't find them later\n- Always include account_id to make images reusable!",
        "operationId": "execute_validate_and_prepare_linkedin_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "account_id": "string",
                  "ad_type": "single_image_horizontal",
                  "image_urls": [
                    "string"
                  ],
                  "organization_id": "string"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for validating and preparing LinkedIn image assets",
                    "properties": {
                      "account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "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).",
                        "title": "Account Id"
                      },
                      "ad_type": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": "single_image_horizontal",
                        "description": "Target ad type for validation. Options: 'single_image_horizontal' (1200x627), 'single_image_square' (1080x1080), 'single_image_vertical' (1200x1500), 'carousel' (1080x1080 per card).",
                        "title": "Ad Type"
                      },
                      "image_urls": {
                        "description": "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).",
                        "items": {
                          "type": "string"
                        },
                        "title": "Image Urls",
                        "type": "array"
                      },
                      "organization_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "LinkedIn Organization (Company Page) ID for image upload. If not provided, will use the primary connected organization.",
                        "title": "Organization Id"
                      }
                    },
                    "required": [
                      "image_urls"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for validate_and_prepare_linkedin_assets)"
                  },
                  "success": true,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: validate_and_prepare_linkedin_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_linkedin_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User provides image URLs to validate BEFORE creating LinkedIn image campaign [write]",
        "tags": [
          "linkedin-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "validate_and_prepare_linkedin_assets"
      }
    },
    "/api/v1/tools/validate_and_prepare_meta_assets/execute": {
      "post": {
        "description": "User wants to upload NEW images for Meta campaigns. This tool validates images and uploads them to Meta to get image hashes.\n\nThis 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).\n\nReturns:\n- Validation results (pass/fail per image)\n- Image hashes for successfully uploaded images\n- Asset bundle ID for use in campaign creation\n- Placement compatibility information\n\nWhen to use this tool:\n- \"Upload this image for my Meta campaign\"\n- \"Validate my product images for Facebook ads\"\n- \"Prepare images for Instagram ads\"\n- User provides image URLs and wants to create a campaign\n\nParameters:\n- image_urls: List of public URLs to validate and upload (1-10 images)\n- placement: Target placement - 'feed' (default), 'stories_reels', or 'carousel'\n- ad_account_id: Required for multi-account users. Get from list_connected_accounts\n\nExecution time: 5-15 seconds (depends on image count and size)\nData source: Meta Ad Image Upload API\n\nImage Requirements:\n| Placement | Aspect Ratio | Min Dimensions | Max Size |\n|-----------|-------------|----------------|----------|\n| Feed | 1:1 or 4:5 | 600x600 | 30MB |\n| Stories/Reels | 9:16 | 500x888 | 30MB |\n| Carousel | 1:1 | 1080x1080 | 30MB |\n\nWorkflow:\n1. Use `validate_and_prepare_meta_assets` with image URLs\n2. If successful, receive an `asset_bundle_id`\n3. Use that bundle_id with `create_meta_image_campaign`",
        "operationId": "execute_validate_and_prepare_meta_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "ad_account_id": "string",
                  "image_urls": [
                    "string"
                  ],
                  "placement": "feed"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for validating and uploading Meta ad images",
                    "properties": {
                      "ad_account_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "Meta Ad Account ID (optional)",
                        "title": "Ad Account Id"
                      },
                      "image_urls": {
                        "description": "List of public image URLs to validate and upload (max 10). Images will be validated and uploaded to Meta to get image hashes.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Image Urls",
                        "type": "array"
                      },
                      "placement": {
                        "default": "feed",
                        "description": "Target placement: 'feed' (default), 'stories_reels', or 'carousel'",
                        "title": "Placement",
                        "type": "string"
                      }
                    },
                    "required": [
                      "image_urls"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for validate_and_prepare_meta_assets)"
                  },
                  "success": true,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: validate_and_prepare_meta_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_meta_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User wants to upload NEW images for Meta campaigns [write]",
        "tags": [
          "meta-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "validate_and_prepare_meta_assets"
      }
    },
    "/api/v1/tools/validate_and_prepare_tiktok_assets/execute": {
      "post": {
        "description": "User provides image URLs to validate BEFORE creating TikTok image campaign.\n\nValidate and prepare new image assets for TikTok ad campaigns.\n\nIMPORTANT: Call this tool BEFORE create_tiktok_campaign if uploading NEW images.\n\nTikTok Image Requirements:\n- Aspect ratio: 9:16 (vertical, e.g., 1080x1920 or 540x960)\n- Maximum file size: 10MB\n- Formats: JPEG, PNG, WEBP\n- Minimum size: 540x960px\n- Recommended: 1080x1920px\n\nParameters:\n- image_urls: List of public image URLs (e.g., from postimages.org, imgbb.com)\n\nReturns:\n- Validation results for each image\n- asset_bundle_id (valid for 60 minutes)\n- Use this bundle ID in create_tiktok_campaign\n\nExecution time: 5-15 seconds (downloads and validates images)\n\nUse this tool to:\n- Upload new images you haven't used before\n- Validate images meet TikTok's specifications\n- Get detailed error messages if images don't meet requirements\n\nAfter validation, use the returned `asset_bundle_id` in `create_tiktok_campaign`.",
        "operationId": "execute_validate_and_prepare_tiktok_assets",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "advertiser_id": "string",
                  "image_urls": [
                    "string"
                  ]
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "properties": {
                      "advertiser_id": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "default": null,
                        "description": "TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts.",
                        "title": "Advertiser Id"
                      },
                      "image_urls": {
                        "description": "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.",
                        "items": {
                          "type": "string"
                        },
                        "title": "Image Urls",
                        "type": "array"
                      }
                    },
                    "required": [
                      "image_urls"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for validate_and_prepare_tiktok_assets)"
                  },
                  "success": true,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: validate_and_prepare_tiktok_assets",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_and_prepare_tiktok_assets"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "User provides image URLs to validate BEFORE creating TikTok image campaign [write]",
        "tags": [
          "tiktok-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": false,
        "x-adspirer-tool-name": "validate_and_prepare_tiktok_assets"
      }
    },
    "/api/v1/tools/validate_video/execute": {
      "post": {
        "description": "Validate video for ad campaigns (unified tool for all platforms).\n\n\u26a0\ufe0f IMPORTANT: This is a READ-ONLY validation tool. Safe to call multiple times.\n\n\ud83c\udfaf **What This Tool Does:**\n- Validates videos for Google Ads PMAX or TikTok campaigns\n- For PMAX: Validates YouTube video (privacy, duration, embeddable)\n- For TikTok: Validates public video URL is accessible\n- Returns metadata (title, duration, privacy, thumbnail)\n\n**DOES NOT upload videos** - user must already have video uploaded:\n- PMAX: Video must be on YouTube (public or unlisted)\n- TikTok: Video must be on public hosting (Google Drive, Vimeo, etc.)\n\n**Parameters:**\n- video_url_or_id (required): YouTube URL/ID (PMAX) or public video URL (TikTok)\n- platform (required): 'pmax' or 'tiktok'\n\n**For PMAX:**\nAccepts YouTube formats:\n- Full URL: https://youtube.com/watch?v=dQw4w9WgXcQ\n- Short URL: https://youtu.be/dQw4w9WgXcQ\n- Shorts: https://youtube.com/shorts/dQw4w9WgXcQ\n- Direct ID: dQw4w9WgXcQ (11 characters)\n\nValidates:\n- Video exists and is accessible\n- Privacy is Public or Unlisted (NOT Private)\n- Embeddable is enabled\n- Duration \u226510 seconds (PMAX requirement)\n\nReturns:\n- Video title\n- Duration\n- Privacy status\n- Thumbnail URL\n- Video ID\n\n**For TikTok:**\nAccepts public video file URLs:\n- Google Drive: https://drive.google.com/file/d/ABC123/view\n- Vimeo: https://vimeo.com/video/123456\n- Dropbox: https://dropbox.com/s/abc/video.mp4\n- Any publicly accessible video URL\n\nValidates:\n- URL is accessible (HTTP 200)\n- Content-Type is video/* (when available)\n\nReturns:\n- URL validation status\n- Content-Type\n- File size (if available)\n\n**Execution Time:** 1-3 seconds (YouTube Data API call or HTTP request)\n\n**When to Use:**\n- BEFORE creating a campaign with videos\n- To verify video meets platform requirements\n- To get video metadata (title, duration)\n\n**Example Usage:**\n\n```\nUser: \"I want to use this YouTube video in my PMAX campaign: dQw4w9WgXcQ\"\nYOU: [Call validate_video with video_url_or_id=\"dQw4w9WgXcQ\", platform=\"pmax\"]\nResponse: Video validated, title=\"Product Demo\", duration=45s, ready for use\n```\n\n```\nUser: \"Can I use this video for TikTok ads: https://drive.google.com/file/d/ABC/view\"\nYOU: [Call validate_video with video_url_or_id=\"https://drive...\", platform=\"tiktok\"]\nResponse: URL validated, accessible, ready for campaign\n```\n\n**Error Handling:**\n- Private videos: Clear error asking user to change privacy to Public/Unlisted\n- Short videos (<10s for PMAX): Error with duration requirement\n- Invalid URLs: Error with accessibility details\n- Not embeddable: Error asking user to enable embedding",
        "operationId": "execute_validate_video",
        "parameters": [
          {
            "description": "Client-generated UUID to make writes idempotent. Strongly recommended for write tools. A repeat call with the same key returns the cached result instead of re-executing. Example: 550e8400-e29b-41d4-a716-446655440000",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "arguments": {
                  "platform": "https://example.com",
                  "video_url_or_id": "https://example.com"
                }
              },
              "schema": {
                "properties": {
                  "arguments": {
                    "description": "Input schema for unified video validation",
                    "properties": {
                      "platform": {
                        "description": "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).",
                        "title": "Platform",
                        "type": "string"
                      },
                      "video_url_or_id": {
                        "description": "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.com/file/d/ABC123/view' (Google Drive video), 'https://vimeo.com/video/123456' (Vimeo video)",
                        "title": "Video Url Or Id",
                        "type": "string"
                      }
                    },
                    "required": [
                      "video_url_or_id",
                      "platform"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "arguments"
                ],
                "type": "object"
              }
            }
          },
          "description": "All tool arguments are wrapped in an `arguments` object. The fields accepted inside `arguments` are listed below \u2014 required fields are marked with a red asterisk.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "quota": {
                      "limit": 150,
                      "period_end": "2026-05-01",
                      "tier": "plus",
                      "used": 42
                    },
                    "text": "(tool-specific textual output for validate_video)"
                  },
                  "success": true,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/SuccessResponse"
                }
              }
            },
            "description": "Tool executed successfully. `data.text` carries the human-readable result (markdown-friendly). `data.quota` shows your current usage against the plan limit. `data.structured` appears when the tool emits machine-parseable structured content. `data.content` appears for tools that return non-text blocks (images, resources)."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "You have 25 meta_ads accounts connected. Please specify which account to use by passing the ad_account_id parameter:\n  - Acme Holdings (ad_account_id=\"act_123456789\")\n  - Acme EU (ad_account_id=\"act_987654321\")",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Tool-level error. The `error` string is safe to surface to end users. Common causes: missing required argument, multi-account user didn't specify which account, upstream platform validation failure."
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authenticated. Please connect your Adspirer account first at https://adspirer.ai",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid, expired, or revoked API key."
          },
          "402": {
            "content": {
              "application/json": {
                "example": {
                  "error": "\ud83d\udea8 Monthly limit reached (150/150 tool calls on Plus tier).\nUpgrade to Pro at https://adspirer.ai to keep building.",
                  "is_error": true,
                  "quota": {
                    "limit": 150,
                    "period_end": "2026-05-01",
                    "tier": "plus",
                    "upgrade_url": "https://adspirer.ai",
                    "used": 150
                  },
                  "success": false,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/QuotaErrorResponse"
                }
              }
            },
            "description": "Quota exhausted for the current billing period. The response includes a `quota` block with the current used/limit/tier values and an `upgrade_url` to move up a tier."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Tool not found: validate_video",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unknown tool_name. Check /openapi.json for the full catalog."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Upstream platform rate limit hit (Meta Business Use Case throttle at 95%). Retry after 60 seconds.",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Rate-limited by the upstream ad platform (Meta, Google, LinkedIn, TikTok). Retry with exponential backoff. Not the same as Adspirer's own quota \u2014 that returns 402."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal error: RuntimeError",
                  "is_error": true,
                  "success": false,
                  "tool": "validate_video"
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unexpected server error. Report to support@adspirer.com with request_id."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Validate video for ad campaigns (unified tool for all platforms)",
        "tags": [
          "google-ads"
        ],
        "x-adspirer-destructive": false,
        "x-adspirer-read-only": true,
        "x-adspirer-tool-name": "validate_video"
      }
    }
  },
  "servers": [
    {
      "description": "Production",
      "url": "https://api.adspirer.ai"
    }
  ],
  "tags": [
    {
      "description": "Audit tools",
      "name": "audit"
    },
    {
      "description": "General (Account Management) tools",
      "name": "general"
    },
    {
      "description": "Google Ads tools",
      "name": "google-ads"
    },
    {
      "description": "LinkedIn Ads tools",
      "name": "linkedin-ads"
    },
    {
      "description": "Meta Ads tools",
      "name": "meta-ads"
    },
    {
      "description": "Monitoring & Reporting tools",
      "name": "monitoring"
    },
    {
      "description": "TikTok Ads tools",
      "name": "tiktok-ads"
    }
  ]
}
