< 1.0). It extends the base wasted spend detector with Meta-specific features:
Returns:
- Campaign-level wasted spend analysis (ROAS
<1.0 = actual losses) - Underperforming spend analysis (1.0
<= ROAS<target = opportunity cost) - Placement-level breakdown (which placements are wasting money: Feed, Stories, Reels, etc.)
- Creative fatigue detection (high frequency + declining CTR)
- Actionable recommendations for campaigns, placements, and creatives
- Quick action items
- “Where am I wasting money on Meta/Facebook ads?”
- “Which Meta placements should I exclude?”
- “Are any of my Facebook creatives fatigued?”
- “How can I reduce wasted ad spend on Instagram?”
- “Which Meta campaigns are losing money?”
- “What’s my Meta ROAS by placement?”
- lookback_days: 7, 30 (default), 60, 90, or 120 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date. ⚠️ DATE CLARIFICATION: If the user’s date request is vague or ambiguous (e.g., “March to June” without a year, “last quarter”, “recently”, “a few months ago”), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- target_roas: Optional override (e.g., 3.0 for 3.0x ROAS)
- include_placements: true (default) - Include placement-level analysis
- include_fatigue: true (default) - Include creative fatigue analysis
- ad_account_id: Required for multi-account users. Get from list_connected_accounts
- Wasted Spend (ROAS
<1.0): Actual money lost - for every $1 spent, getting back `<`$1 - Underperforming (1.0
<= ROAS<target): Profitable but below target - opportunity cost - Creative Fatigue: Ads with frequency >4x (cold traffic) or >7x (retargeting) showing declining CTR
- Audience Network often has lowest ROAS - consider excluding
- Instagram Stories/Reels typically perform differently than Feed
- Facebook Marketplace can waste spend if not relevant
- ⏳ LEARNING campaigns → Do NOT pause. Monitor for 14+ days before judging.
- ❓ INSUFFICIENT_DATA campaigns → Need more spend before analysis is meaningful.
- 🚨 CRITICAL campaigns (established, 14+ days, ZERO conversions) → Consider pausing
- 🚨 CRITICAL campaigns (established, 14+ days, HAS conversions) → Review performance, verify revenue in ad platform before reducing budget
- 🔴 HIGH severity (established, 14+ days) → Consider reducing budget by 50-70%
- 🟡 MEDIUM → Optimize targeting, ad copy, landing pages
OpenAPI
/api-reference/openapi.json post /api/v1/tools/analyze_meta_wasted_spend/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/analyze_meta_wasted_spend/execute:
post:
tags:
- meta-ads
summary: Analyze Meta Wasted Spend
description: >-
User asks about Meta/Facebook/Instagram ad spend efficiency, wasted
money, underperforming campaigns, placement optimization, or creative
fatigue.
This tool identifies Meta Ads campaigns and placements that are wasting
money by performing below target ROAS or below breakeven (ROAS `<` 1.0).
It extends the base wasted spend detector with Meta-specific features:
Returns:
- Campaign-level wasted spend analysis (ROAS `<` 1.0 = actual losses)
- Underperforming spend analysis (1.0 `<`= ROAS `<` target = opportunity
cost)
- Placement-level breakdown (which placements are wasting money: Feed,
Stories, Reels, etc.)
- Creative fatigue detection (high frequency + declining CTR)
- Actionable recommendations for campaigns, placements, and creatives
- Quick action items
When to use this tool:
- "Where am I wasting money on Meta/Facebook ads?"
- "Which Meta placements should I exclude?"
- "Are any of my Facebook creatives fatigued?"
- "How can I reduce wasted ad spend on Instagram?"
- "Which Meta campaigns are losing money?"
- "What's my Meta ROAS by placement?"
Parameters:
- lookback_days: 7, 30 (default), 60, 90, or 120 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days
when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when
used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous
(e.g., "March to June" without a year, "last quarter", "recently", "a
few months ago"), ask the user to specify exact dates before calling
this tool. Do not assume or guess dates.
- target_roas: Optional override (e.g., 3.0 for 3.0x ROAS)
- include_placements: true (default) - Include placement-level analysis
- include_fatigue: true (default) - Include creative fatigue analysis
- ad_account_id: Required for multi-account users. Get from
list_connected_accounts
Execution time: 2-5 seconds (cached database query with analysis)
Data source: campaign_daily_metrics + meta_placement_daily_metrics +
meta_ad_creative_metrics tables
Key definitions:
- Wasted Spend (ROAS `<` 1.0): Actual money lost - for every $1 spent,
getting back `<`$1
- Underperforming (1.0 `<`= ROAS `<` target): Profitable but below
target - opportunity cost
- Creative Fatigue: Ads with frequency >4x (cold traffic) or >7x
(retargeting) showing declining CTR
Meta-specific placement optimization:
- Audience Network often has lowest ROAS - consider excluding
- Instagram Stories/Reels typically perform differently than Feed
- Facebook Marketplace can waste spend if not relevant
**Quick Actions (IMPORTANT — read severity context first):**
- ⏳ LEARNING campaigns → Do NOT pause. Monitor for 14+ days before
judging.
- ❓ INSUFFICIENT_DATA campaigns → Need more spend before analysis is
meaningful.
- 🚨 CRITICAL campaigns (established, 14+ days, ZERO conversions) →
Consider pausing
- 🚨 CRITICAL campaigns (established, 14+ days, HAS conversions) →
Review performance, verify revenue in ad platform before reducing budget
- 🔴 HIGH severity (established, 14+ days) → Consider reducing budget by
50-70%
- 🟡 MEDIUM → Optimize targeting, ad copy, landing pages
⚠️ **NEVER say "pause" for a campaign that has conversions.** Say
"review" or "reduce budget" instead.
⚠️ **NEVER recommend pausing a campaign in LEARNING phase.**
⚠️ **If ALL campaigns are LEARNING or INSUFFICIENT_DATA, tell the user
their account is too new for waste analysis and recommend checking back
in 2 weeks.**
⚠️ **Consider campaign objective: brand awareness campaigns will not
have ROAS data. This is normal.**
⚠️ **When data confidence is MEDIUM or LOW, soften all recommendations
and add verification prompts.**
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
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 — 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:
- Acme Holdings (ad_account_id="act_123456789")
- 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 ADSMCP account first
at https://adsmcp.si
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: |-
🚨 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: 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 ADSMCP's
own quota — 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 [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