# Paid ads & creative

**Read-only tools.** Paid-ad performance at every grain (organization, day, account, campaign, ad set, ad, keyword, geography) plus creative fatigue and the creative leaderboard.

| Tool | Purpose |
|---|---|
| [`get_performance_summary`](#get_performance_summary) | Ad performance summary |
| [`get_daily_performance`](#get_daily_performance) | Daily ad performance |
| [`get_accounts_performance`](#get_accounts_performance) | Performance by ad account |
| [`get_campaigns_performance`](#get_campaigns_performance) | Performance by campaign |
| [`get_adsets_performance`](#get_adsets_performance) | Performance by ad set |
| [`get_ads_performance`](#get_ads_performance) | Performance by ad |
| [`get_keywords_performance`](#get_keywords_performance) | Performance by keyword |
| [`get_geo_performance`](#get_geo_performance) | Performance by geography |
| [`get_creative_fatigue`](#get_creative_fatigue) | Fatigued ads |
| [`get_creative_leaderboard`](#get_creative_leaderboard) | Creative leaderboard |

## `get_performance_summary`

**Ad performance summary**

Organization-wide paid-ad totals for one window: spend, impressions, clicks, ctr, cpm, cpc, roas, fatigue_score and this organization's custom metrics (metric_1..metric_10). This is the grounding call for "how are we doing" — start here, then drill in with the per-entity tools. Optional filters narrow to one product, network, ad account or campaign. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns. Read get_metric_definitions before interpreting or comparing any metric_N column: the slots mean different things per organization.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_performance_summary`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_daily_performance`

**Daily ad performance**

Per-day paid-ad performance series for a window, filterable to one product, network, ad account, campaign, ad set or ad. Use it for trends, pacing and "what changed on &lt;date&gt;". Omit both dates for the default window: the last 30 complete days ending yesterday. Rows are complete days. If you bucket days into weeks, use MONDAY-start weeks — that is the week boundary the Bellaso app and every other report in this product use. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_daily_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_id` | string | no | Filter to one ad id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `adset_id` | string | no | Filter to one ad set id. (length 1–128) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_accounts_performance`

**Performance by ad account**

Paid-ad performance rolled up per ad account, including this organization's custom KPI counts and values. Use it to compare accounts or to find which account carries the spend. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_accounts_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `limit` | integer | no | Max rows to return after sorting (default 50, max 200). (1–200) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `sort_by` | string | no | Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step. (length 1–64) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_campaigns_performance`

**Performance by campaign**

Paid-ad performance rolled up per campaign — "top campaigns", "which campaigns are underperforming", "where did the spend go". Filter to one network or account first when the question is scoped that way. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_campaigns_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `limit` | integer | no | Max rows to return after sorting (default 50, max 200). (1–200) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `sort_by` | string | no | Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step. (length 1–64) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_adsets_performance`

**Performance by ad set**

Paid-ad performance rolled up per ad set, optionally inside one campaign (campaign_id) or for one ad set (adset_id). Use it after a campaign-level answer, to see which audience or placement inside the campaign is doing the work. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_adsets_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `adset_id` | string | no | Filter to one ad set id. (length 1–128) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `limit` | integer | no | Max rows to return after sorting (default 50, max 200). (1–200) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `sort_by` | string | no | Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step. (length 1–64) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_ads_performance`

**Performance by ad**

Paid-ad performance rolled up per individual ad. Ad grain exists for facebook_ads, linkedin_ads and tiktok_ads only — other networks do not report it, so an empty result for google_ads or bingads is expected rather than a data problem. For CRM-source organizations these rows are CRM-attributed at ad grain and match the platform's Ads page, which makes this THE authoritative source for per-ad and per-creative MQL ranking: rank by metric_1 (see get_metric_definitions). Never take that ranking from get_creative_fatigue or get_creative_leaderboard — their conversion columns are platform-pixel and near zero by design for those organizations. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_ads_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `adset_id` | string | no | Filter to one ad set id. (length 1–128) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `limit` | integer | no | Max rows to return after sorting (default 50, max 200). (1–200) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `sort_by` | string | no | Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step. (length 1–64) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_keywords_performance`

**Performance by keyword**

Keyword-level performance with match_type, for google_ads and bingads only (the social networks have no keyword grain, so filtering this to one of them returns nothing by design). Filter with campaign_id / adset_id to scope to one ad group, or keyword_id for a single keyword. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_keywords_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `adset_id` | string | no | Filter to one ad set id. (length 1–128) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `keyword_id` | string | no | Filter to one keyword id. (length 1–128) |
| `limit` | integer | no | Max rows to return after sorting (default 50, max 200). (1–200) |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `sort_by` | string | no | Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step. (length 1–64) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_geo_performance`

**Performance by geography**

Country-level spend, clicks and conversions, rolled up either by ad account (level="account", the default) or by campaign (level="campaign"). country_code narrows to a single country. Coverage depends on what each network reports geographically, so a country missing from the rows means "not reported", not "zero". Omit both dates for the default window: the last 30 complete days ending yesterday.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_geo_performance`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `account_id` | string | no | Filter to one ad account id. (length 1–128) |
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `campaign_id` | string | no | Filter to one campaign id. (length 1–128) |
| `country_code` | string | no | Filter to one country, as its 2- or 3-letter code (e.g. US). (length 2–3, pattern `^[A-Za-z]{2,3}$`) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `level` | `account` \| `campaign` | no | Rollup grain: 'account' (default) or 'campaign'. |
| `product_id` | string | no | Filter to one product id from the org context. (length 1–128) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_creative_fatigue`

**Fatigued ads**

Ads whose creative fatigue score is elevated, highest first — the tool for "which ads are burning out / need a refresh". The score adds a CTR-decay component (up to 50), an impression-pressure component (up to 30) and a creative-age component (up to 20), and an ad younger than 7 days always scores 0. min_score defaults to 30; only ads with spend in the window are considered, and at most 200 rows come back. This reads engagement and delivery metrics plus PLATFORM-PIXEL conversions: for CRM-source organizations the conversion columns here are near zero by design, so never rank creatives by MQL or lead volume with this tool — use get_ads_performance. Omit both dates for the default window: the last 30 complete days ending yesterday.

**Posture:** read-only · **Module gate:** `ads` · **REST:** `POST /api/v1/tools/get_creative_fatigue`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `ad_network_id` | string | no | Filter to one network: facebook_ads \| google_ads \| tiktok_ads \| linkedin_ads \| bingads. (length 1–64) |
| `end_date` | string | no | Window end, YYYY-MM-DD (inclusive). (pattern `^\d{4}-\d{2}-\d{2}$`) |
| `min_score` | number | no | Minimum fatigue score to include, 0-100 (default 30). (0–100) |
| `start_date` | string | no | Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday. (pattern `^\d{4}-\d{2}-\d{2}$`) |

## `get_creative_leaderboard`

**Creative leaderboard**

Per-creative leaderboard with winner / mid / loser labels, trend, spend, CTR, KPI count, cost per KPI and the extracted creative attributes (visual style, messaging angle, density, specificity, human presence, subject elements) — the tool for "what is working creatively" and "what do our winners have in common". window_days accepts only 30, 60, 90 or 120 (attributes are precomputed nightly for those windows); it takes no start_date/end_date. Its metrics are ad-platform reported, so for CRM-source organizations the KPI columns are near zero by design — rank creatives by MQL with get_ads_performance instead. Requires the Creative module: without it this returns a clean "not part of this organization's plan" refusal rather than empty data.

**Posture:** read-only · **Module gate:** `creative` · **REST:** `POST /api/v1/tools/get_creative_leaderboard`

| Argument | Type | Required | Notes |
|---|---|---|---|
| `limit` | integer | no | Rows to return (default 25, max 100). (1–100) |
| `sort` | `cost_per_kpi` \| `spend` \| `ctr` \| `kpis` | no | Ranking column (default cost_per_kpi). cost_per_kpi orders cheapest-first (best first); spend, ctr and kpis order highest-first. |
| `window_days` | `30` \| `60` \| `90` \| `120` | no | Lookback window in days. Only 30, 60, 90 and 120 exist (the attributes are precomputed nightly for those windows); default 90. |
