- MUST have valid asset_bundle_id from validate_and_prepare_assets tool OR existing_image_ids from discover_existing_assets
- MUST have all campaign text details from user
- Creates REAL campaigns that cost REAL money
- Call ONLY ONCE per campaign - do NOT retry automatically
- Videos significantly improve PMAX performance (higher CTR, better engagement)
- Always ask the user: “Would you like to add YouTube videos to this campaign?”
- If yes: validate with
validate_videotool, then includeyoutube_video_idsparameter - If no: proceed without videos (campaigns work fine either way)
- See STEP 3.5 below for complete video guidance
OpenAPI
/api-reference/openapi.json post /api/v1/tools/create_pmax_campaign/execute
openapi: 3.1.0
info:
contact:
email: [email protected]
name: ADSMCP Support
description: >-
REST endpoints for every ADSMCP 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).
## Envelope
Every request wraps tool-specific input in an `arguments` object:
```json
{ "arguments": { <tool-specific fields> } }
```
Every response wraps the result in either a success envelope (`success:
true`, `data: {...}`) or an error envelope (`success: false`, `error:
"..."`, `is_error: true`).
## Authentication
Pass your API key as `Authorization: Bearer sk_live_...` on every request.
Generate keys at https://adsmcp.si/dashboard/api-tokens.
## Quota & billing
Every successful billable call decrements your monthly tool-call allowance.
The current counter is attached to every 200 response under `data.quota`:
```json
"quota": { "used": 42, "limit": 150, "tier": "plus", "period_end":
"2026-05-01" }
```
When 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.
## Idempotency
Write 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** — it
prevents duplicate campaigns when networks misbehave. Generate a fresh UUID
per logical operation (not per retry).
## Multi-account users
Customers 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.
## HTTP status codes
- `200` — success (parse `data`)
- `400` — tool-level error (surface `error` to users)
- `401` — bad/missing API key
- `402` — ADSMCP quota exhausted
- `404` — unknown tool name
- `429` — upstream ad platform rate-limited us (Meta/Google/etc.) — retry
with backoff
- `500` — server error, report to support
## Streaming
This 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: ADSMCP REST API
version: 1.0.0
servers:
- description: Production
url: https://api.adsmcp.si
security: []
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
paths:
/api/v1/tools/create_pmax_campaign/execute:
post:
tags:
- google-ads
summary: Create PMax Campaign
description: >
🔄 LONG-RUNNING TOOL: Creates a Google Performance Max campaign with
validated images. Emits MCP progress updates during authentication,
asset upload, and campaign creation (typically 15-30 seconds). Progress
stages: validate → commit.
⚠️ CRITICAL PREREQUISITES:
1. MUST have valid asset_bundle_id from validate_and_prepare_assets tool
OR existing_image_ids from discover_existing_assets
2. MUST have all campaign text details from user
3. Creates REAL campaigns that cost REAL money
4. Call ONLY ONCE per campaign - do NOT retry automatically
🎥 **VIDEOS ARE OPTIONAL BUT RECOMMENDED:**
- Videos significantly improve PMAX performance (higher CTR, better
engagement)
- **Always ask the user**: "Would you like to add YouTube videos to this
campaign?"
- If yes: validate with `validate_video` tool, then include
`youtube_video_ids` parameter
- If no: proceed without videos (campaigns work fine either way)
- See STEP 3.5 below for complete video guidance
⚠️ GOOGLE ADS POLICY NOTE:
Avoid keywords related to health conditions, medical treatments,
financial hardship, or political topics. These may trigger policy
violations. Use general service terms instead.
Example: Use "senior care services" not "nursing care", "home services"
not "medical services"
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
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.
Supports two modes:
1. Single asset group (flat fields — backward compatible)
2. Multiple asset groups (asset_groups array — #183)
If 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': 'ADSMCP',
'final_url': 'https://adsmcp.si', '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 — pass the user's
amount as-is. Example: if user says '₹5000/day', pass
5000 (not a USD conversion). The Google Ads API
interprets this in the account's currency automatically.
Google recommends minimum ~$50/day USD equivalent for
Performance Max campaigns.
minimum: 1
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. ⚠️ MUST include 'logos_square' — 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 — 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 — 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:
- Acme Holdings (ad_account_id="act_123456789")
- 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 ADSMCP account first
at https://adsmcp.si
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: |-
🚨 Monthly limit reached (150/150 tool calls on Plus tier).
Upgrade to Pro at https://adsmcp.si to keep building.
is_error: true
quota:
limit: 150
period_end: '2026-05-01'
tier: plus
upgrade_url: https://adsmcp.si
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 ADSMCP's
own quota — 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 [email protected] with
request_id.
security:
- ApiKeyAuth: []
components:
schemas:
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
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
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
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
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
securitySchemes:
ApiKeyAuth:
bearerFormat: API Key (sk_live_...)
description: >-
API key from https://adsmcp.si/dashboard/api-tokens. Prefix `sk_live_`. Treat as a
secret — never commit.
scheme: bearer
type: http