@channel47/tiktok-ads-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import {
5
+ CallToolRequestSchema,
6
+ GetPromptRequestSchema,
7
+ ListPromptsRequestSchema,
8
+ ListResourcesRequestSchema,
9
+ ListToolsRequestSchema,
10
+ ReadResourceRequestSchema
11
+ } from '@modelcontextprotocol/sdk/types.js';
12
+
13
+ import { validateEnvironment } from './auth.js';
14
+ import { listAccounts } from './tools/list-accounts.js';
15
+ import { query } from './tools/query.js';
16
+ import { report } from './tools/report.js';
17
+ import { mutate } from './tools/mutate.js';
18
+ import { getPromptsList, renderPrompt } from './prompts/templates.js';
19
+ import { getResourcesList, readResource } from './resources/index.js';
20
+
21
+ const SERVER_NAME = 'tiktok-ads-mcp';
22
+ const SERVER_VERSION = '0.1.0';
23
+ const READ_ONLY = process.env.TIKTOK_ADS_READ_ONLY === 'true';
24
+
25
+ const ALL_TOOLS = [
26
+ {
27
+ name: 'list_accounts',
28
+ description: 'List accessible TikTok ad accounts (advertisers) with name, status, currency, and timezone. Discovers advertiser ids via /oauth2/advertiser/get/ when TIKTOK_ADS_APP_ID and TIKTOK_ADS_APP_SECRET are set; otherwise pass advertiser_ids or set TIKTOK_ADS_ADVERTISER_ID.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ advertiser_ids: {
33
+ type: 'array',
34
+ items: { type: 'string' },
35
+ description: 'Explicit advertiser ids to look up (skips oauth2 discovery)'
36
+ }
37
+ }
38
+ }
39
+ },
40
+ {
41
+ name: 'query',
42
+ description: 'Query campaigns, adgroups, or ads from the TikTok Business API with optional filtering, field selection, and pagination.',
43
+ inputSchema: {
44
+ type: 'object',
45
+ properties: {
46
+ advertiser_id: {
47
+ type: 'string',
48
+ description: 'TikTok advertiser ID (defaults to TIKTOK_ADS_ADVERTISER_ID)'
49
+ },
50
+ entity: {
51
+ type: 'string',
52
+ enum: ['campaigns', 'adgroups', 'ads'],
53
+ description: 'Entity type to query'
54
+ },
55
+ fields: {
56
+ anyOf: [
57
+ { type: 'string' },
58
+ {
59
+ type: 'array',
60
+ items: { type: 'string' }
61
+ }
62
+ ],
63
+ description: 'Optional fields selection. Defaults to entity-specific presets.'
64
+ },
65
+ filtering: {
66
+ type: 'object',
67
+ description: 'Optional TikTok filtering object (e.g. { "campaign_ids": ["123"], "primary_status": "STATUS_DELIVERY_OK" })'
68
+ },
69
+ limit: {
70
+ type: 'integer',
71
+ minimum: 1,
72
+ maximum: 1000,
73
+ description: 'Maximum rows to return (default: 100, max: 1000)'
74
+ }
75
+ },
76
+ required: ['entity']
77
+ }
78
+ },
79
+ {
80
+ name: 'report',
81
+ description: 'Run a synchronous TikTok integrated report (GET /report/integrated/get/) with dimensions, metrics, and a date range or lifetime scope. Rows are returned with dimensions and metrics flattened together.',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ advertiser_id: {
86
+ type: 'string',
87
+ description: 'TikTok advertiser ID (defaults to TIKTOK_ADS_ADVERTISER_ID)'
88
+ },
89
+ report_type: {
90
+ type: 'string',
91
+ enum: ['BASIC', 'AUDIENCE'],
92
+ description: 'Report type (default: BASIC)'
93
+ },
94
+ data_level: {
95
+ type: 'string',
96
+ enum: ['AUCTION_ADVERTISER', 'AUCTION_CAMPAIGN', 'AUCTION_ADGROUP', 'AUCTION_AD'],
97
+ description: 'Aggregation level (default: AUCTION_CAMPAIGN)'
98
+ },
99
+ dimensions: {
100
+ type: 'array',
101
+ items: { type: 'string' },
102
+ description: 'Report dimensions. Defaults to the data_level id dimension plus stat_time_day (id dimension only when lifetime=true).'
103
+ },
104
+ metrics: {
105
+ type: 'array',
106
+ items: { type: 'string' },
107
+ description: 'Report metrics (default: spend, impressions, clicks, ctr, cpc, cpm, conversion, cost_per_conversion, conversion_rate)'
108
+ },
109
+ start_date: {
110
+ type: 'string',
111
+ description: 'Start date YYYY-MM-DD (defaults to 7 days ago when lifetime is false)'
112
+ },
113
+ end_date: {
114
+ type: 'string',
115
+ description: 'End date YYYY-MM-DD (defaults to today UTC when lifetime is false)'
116
+ },
117
+ lifetime: {
118
+ type: 'boolean',
119
+ description: 'Query lifetime metrics instead of a date range (default: false; excludes stat_time dimensions)'
120
+ },
121
+ filtering: {
122
+ type: 'array',
123
+ items: { type: 'object' },
124
+ description: 'Optional filter clauses: [{ "field_name", "filter_type", "filter_value" }]'
125
+ },
126
+ order_field: {
127
+ type: 'string',
128
+ description: 'Optional metric/dimension to sort by (e.g. spend)'
129
+ },
130
+ order_type: {
131
+ type: 'string',
132
+ enum: ['ASC', 'DESC'],
133
+ description: 'Sort direction when order_field is set (default: DESC)'
134
+ },
135
+ limit: {
136
+ type: 'integer',
137
+ minimum: 1,
138
+ maximum: 1000,
139
+ description: 'Maximum rows to return (default: 100, max: 1000)'
140
+ }
141
+ }
142
+ }
143
+ },
144
+ {
145
+ name: 'mutate',
146
+ description: 'Execute create/update/pause/enable/delete operations for campaign, adgroup, and ad entities. dry_run defaults to true and performs local validation plus a preview of the exact requests (TikTok has no server-side validate-only mode). Campaign and adgroup creates default operation_status to DISABLE (paused) for safety. DELETE is permanent and unrecoverable — prefer pause. Ad updates take the full /ad/update/ body in params (ads are identified via creatives[].ad_id).',
147
+ inputSchema: {
148
+ type: 'object',
149
+ properties: {
150
+ advertiser_id: {
151
+ type: 'string',
152
+ description: 'TikTok advertiser ID (defaults to TIKTOK_ADS_ADVERTISER_ID)'
153
+ },
154
+ operations: {
155
+ type: 'array',
156
+ description: 'Array of operation objects: { entity, action, id?, params? }',
157
+ items: {
158
+ type: 'object'
159
+ }
160
+ },
161
+ dry_run: {
162
+ type: 'boolean',
163
+ description: 'Validate locally and preview requests only (default: true)',
164
+ default: true
165
+ },
166
+ partial_failure: {
167
+ type: 'boolean',
168
+ description: 'Continue executing later operations when one fails (default: true)',
169
+ default: true
170
+ }
171
+ },
172
+ required: ['operations']
173
+ }
174
+ }
175
+ ];
176
+
177
+ const TOOLS = READ_ONLY ? ALL_TOOLS.filter((tool) => tool.name !== 'mutate') : ALL_TOOLS;
178
+
179
+ const envStatus = validateEnvironment();
180
+ if (!envStatus.valid) {
181
+ console.error(`Missing required environment variables: ${envStatus.missing.join(', ')}`);
182
+ process.exit(1);
183
+ }
184
+
185
+ if (READ_ONLY) {
186
+ console.error('Read-only mode enabled — mutate tool disabled');
187
+ }
188
+
189
+ const server = new Server(
190
+ {
191
+ name: SERVER_NAME,
192
+ version: SERVER_VERSION
193
+ },
194
+ {
195
+ capabilities: {
196
+ tools: {},
197
+ prompts: {},
198
+ resources: {}
199
+ }
200
+ }
201
+ );
202
+
203
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
204
+
205
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
206
+ const { name, arguments: params } = request.params;
207
+
208
+ if (name === 'list_accounts') {
209
+ return listAccounts(params);
210
+ }
211
+
212
+ if (name === 'query') {
213
+ return query(params);
214
+ }
215
+
216
+ if (name === 'report') {
217
+ return report(params);
218
+ }
219
+
220
+ if (name === 'mutate') {
221
+ if (READ_ONLY) {
222
+ throw new Error('mutate is disabled in read-only mode (TIKTOK_ADS_READ_ONLY=true)');
223
+ }
224
+ return mutate(params);
225
+ }
226
+
227
+ throw new Error(`Unknown tool: ${name}`);
228
+ });
229
+
230
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: getPromptsList() }));
231
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => (
232
+ renderPrompt(request.params.name, request.params.arguments || {})
233
+ ));
234
+
235
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: getResourcesList() }));
236
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => readResource(request.params.uri));
237
+
238
+ async function main() {
239
+ const transport = new StdioServerTransport();
240
+ await server.connect(transport);
241
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} started`);
242
+ }
243
+
244
+ main().catch((error) => {
245
+ console.error('Fatal error:', error);
246
+ process.exit(1);
247
+ });
@@ -0,0 +1,127 @@
1
+ export const PROMPT_TEMPLATES = {
2
+ spend_pacing_check: {
3
+ name: 'spend_pacing_check',
4
+ description: 'Check campaign pacing against recent spend and budget signals',
5
+ arguments: [
6
+ {
7
+ name: 'advertiser_id',
8
+ description: 'TikTok advertiser ID',
9
+ required: true
10
+ },
11
+ {
12
+ name: 'lookback_days',
13
+ description: 'Number of trailing days to analyze',
14
+ required: false,
15
+ default: '7'
16
+ }
17
+ ],
18
+ template: `Run a spend pacing check for TikTok advertiser {{advertiser_id}} over the last {{lookback_days}} days:
19
+
20
+ 1. Pull campaign-level report rows (spend, impressions, clicks, conversion, cost_per_conversion) with stat_time_day.
21
+ 2. Pull campaign budgets and budget_mode via query.
22
+ 3. Compare daily spend velocity to available daily/lifetime budgets.
23
+ 4. Flag campaigns underpacing and overpacing.
24
+ 5. Highlight campaigns spending with weak conversion signals.
25
+ 6. Return top pacing issues and recommended budget actions.`,
26
+ requiredTools: ['query', 'report']
27
+ },
28
+ creative_fatigue_check: {
29
+ name: 'creative_fatigue_check',
30
+ description: 'Check for ad creative fatigue signals and propose refresh priorities',
31
+ arguments: [
32
+ {
33
+ name: 'advertiser_id',
34
+ description: 'TikTok advertiser ID',
35
+ required: true
36
+ },
37
+ {
38
+ name: 'lookback_days',
39
+ description: 'Number of trailing days to analyze',
40
+ required: false,
41
+ default: '14'
42
+ }
43
+ ],
44
+ template: `Run a creative fatigue analysis for TikTok advertiser {{advertiser_id}} using the last {{lookback_days}} days of data:
45
+
46
+ 1. Pull ad-level report rows (spend, impressions, clicks, ctr, conversion) with stat_time_day.
47
+ 2. Flag ads with declining CTR and rising cost_per_conversion week over week.
48
+ 3. Group fatigued ads by campaign and ad group.
49
+ 4. Identify top creatives to refresh first by wasted spend.
50
+ 5. Return prioritized creative refresh recommendations with rationale.`,
51
+ requiredTools: ['query', 'report']
52
+ },
53
+ account_performance_audit: {
54
+ name: 'account_performance_audit',
55
+ description: 'Audit overall account structure and performance for quick wins',
56
+ arguments: [
57
+ {
58
+ name: 'advertiser_id',
59
+ description: 'TikTok advertiser ID',
60
+ required: true
61
+ },
62
+ {
63
+ name: 'lookback_days',
64
+ description: 'Number of trailing days to analyze',
65
+ required: false,
66
+ default: '30'
67
+ }
68
+ ],
69
+ template: `Run an account performance audit for TikTok advertiser {{advertiser_id}} over the last {{lookback_days}} days:
70
+
71
+ 1. List campaigns, ad groups, and ads with statuses via query.
72
+ 2. Pull campaign- and adgroup-level report rows (spend, impressions, clicks, ctr, cpc, conversion, cost_per_conversion).
73
+ 3. Identify paused or rejected entities blocking delivery (secondary_status).
74
+ 4. Rank campaigns by efficiency (cost_per_conversion) and spend concentration.
75
+ 5. Flag structural issues (single-ad ad groups, overlapping schedules, exhausted budgets).
76
+ 6. Return a prioritized list of quick wins with expected impact.`,
77
+ requiredTools: ['query', 'report']
78
+ }
79
+ };
80
+
81
+ export function getPromptDefinition(name) {
82
+ return PROMPT_TEMPLATES[name] || null;
83
+ }
84
+
85
+ export function getPromptsList() {
86
+ return Object.values(PROMPT_TEMPLATES).map((prompt) => ({
87
+ name: prompt.name,
88
+ description: prompt.description,
89
+ arguments: prompt.arguments
90
+ }));
91
+ }
92
+
93
+ export function renderPrompt(name, args = {}) {
94
+ const template = PROMPT_TEMPLATES[name];
95
+ if (!template) {
96
+ throw new Error(`Unknown prompt: ${name}. Available prompts: ${Object.keys(PROMPT_TEMPLATES).join(', ')}`);
97
+ }
98
+
99
+ const missing = template.arguments
100
+ .filter((argument) => argument.required && !args[argument.name])
101
+ .map((argument) => argument.name);
102
+
103
+ if (missing.length > 0) {
104
+ throw new Error(`Missing required arguments for prompt "${name}": ${missing.join(', ')}`);
105
+ }
106
+
107
+ let text = template.template;
108
+
109
+ for (const argument of template.arguments) {
110
+ const value = args[argument.name] ?? argument.default;
111
+ if (value !== undefined) {
112
+ text = text.replace(new RegExp(`\\{\\{${argument.name}\\}\\}`, 'g'), String(value));
113
+ }
114
+ }
115
+
116
+ return {
117
+ messages: [
118
+ {
119
+ role: 'user',
120
+ content: {
121
+ type: 'text',
122
+ text
123
+ }
124
+ }
125
+ ]
126
+ };
127
+ }
@@ -0,0 +1,72 @@
1
+ # TikTok Business API Quick Reference
2
+
3
+ ## Base URL
4
+
5
+ `https://business-api.tiktok.com/open_api/v1.3`
6
+
7
+ ## Authentication
8
+
9
+ - Auth is provided via the `Access-Token` request header (not `Authorization: Bearer`)
10
+ - This server reads the token from `TIKTOK_ADS_ACCESS_TOKEN`
11
+ - Tokens issued for the Business API are long-lived; no refresh flow in v1
12
+ - `TIKTOK_ADS_APP_ID` + `TIKTOK_ADS_APP_SECRET` (developer app credentials) are only needed for advertiser discovery via `/oauth2/advertiser/get/`
13
+
14
+ ## Response Envelope
15
+
16
+ Every response — including errors — is normally HTTP `200` with:
17
+
18
+ ```json
19
+ {
20
+ "code": 0,
21
+ "message": "OK",
22
+ "request_id": "...",
23
+ "data": { }
24
+ }
25
+ ```
26
+
27
+ - `code: 0` means success; any non-zero `code` is an error
28
+ - This server converts non-zero codes into thrown errors that include the code and message
29
+ - List endpoints return `data.list` plus `data.page_info { page, page_size, total_number, total_page }`
30
+
31
+ ## Account Discovery
32
+
33
+ - Authorized advertisers: `GET /oauth2/advertiser/get/` with `app_id` and `secret` query params (plus `Access-Token` header). Returns `data.list` of `{ advertiser_id, advertiser_name }`
34
+ - Account details: `GET /advertiser/info/` with `advertiser_ids` (JSON-encoded array, max 100 ids) and optional `fields` (JSON-encoded array, e.g. `name`, `status`, `currency`, `timezone`, `company`, `country`, `create_time`)
35
+
36
+ ## Query Endpoints (by Advertiser)
37
+
38
+ - Campaigns: `GET /campaign/get/`
39
+ - Ad groups: `GET /adgroup/get/`
40
+ - Ads: `GET /ad/get/`
41
+
42
+ Useful query params:
43
+
44
+ - `advertiser_id` (required)
45
+ - `fields`: JSON-encoded array of field names
46
+ - `filtering`: JSON-encoded object (e.g. `{"campaign_ids": ["123"], "primary_status": "STATUS_DELIVERY_OK"}`)
47
+ - `page` (default 1) and `page_size` (default 10, max 1000)
48
+
49
+ Complex GET params (`fields`, `filtering`, `dimensions`, `metrics`, `advertiser_ids`) must be JSON-encoded strings in the query string.
50
+
51
+ ## Mutations
52
+
53
+ All writes are `POST` with a JSON body that includes `advertiser_id`:
54
+
55
+ - Create: `POST /campaign/create/`, `POST /adgroup/create/`, `POST /ad/create/`
56
+ - Update: `POST /campaign/update/` (`campaign_id`), `POST /adgroup/update/` (`adgroup_id`), `POST /ad/update/` (identifies ads via `creatives[].ad_id`)
57
+ - Status: `POST /campaign/status/update/` (`campaign_ids`), `POST /adgroup/status/update/` (`adgroup_ids`), `POST /ad/status/update/` (`ad_ids`) with `operation_status`: `ENABLE`, `DISABLE`, or `DELETE` (max 100 ids per call)
58
+
59
+ Notes:
60
+
61
+ - `/campaign/create/` requires `campaign_name` and `objective_type`; `operation_status` defaults to `ENABLE` server-side — this server defaults it to `DISABLE` (paused)
62
+ - `/adgroup/create/` requires `campaign_id`, `adgroup_name`, `billing_event`, `budget`, `budget_mode`, `optimization_goal`, `pacing`, `schedule_type`, `schedule_start_time`
63
+ - `/ad/create/` requires `adgroup_id` and a `creatives` array; there is no top-level `operation_status`
64
+ - `DELETE` via status update is permanent — prefer `DISABLE` (pause)
65
+ - There is no validate-only/dry-run mode in the API; this server's dry run previews requests locally
66
+
67
+ ## Error Handling
68
+
69
+ - Non-zero `code` on HTTP 200 is an API error (message in `message`)
70
+ - `code 40100` indicates rate limiting (see the rate limits resource)
71
+ - Common auth errors: invalid/expired token, advertiser not authorized for the token
72
+ - Budgets and money metrics are denominated in the advertiser account currency (major units)
@@ -0,0 +1,65 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ /**
9
+ * Static list of resource documents exposed by the server.
10
+ */
11
+ export const RESOURCES = [
12
+ {
13
+ uri: 'tiktokads://reference',
14
+ name: 'TikTok Business API Reference',
15
+ description: 'Core Business API endpoints, request conventions, and error handling notes for TikTok Ads workflows',
16
+ mimeType: 'text/markdown'
17
+ },
18
+ {
19
+ uri: 'tiktokads://reporting',
20
+ name: 'TikTok Ads Reporting Reference',
21
+ description: 'Integrated report dimensions, metrics, data levels, and date handling',
22
+ mimeType: 'text/markdown'
23
+ },
24
+ {
25
+ uri: 'tiktokads://rate-limits',
26
+ name: 'TikTok Ads Rate Limits and Quotas',
27
+ description: 'Rate limiting behavior, backoff guidance, and operational recommendations',
28
+ mimeType: 'text/markdown'
29
+ }
30
+ ];
31
+
32
+ const RESOURCE_FILE_MAP = {
33
+ 'tiktokads://reference': 'business-api-reference.md',
34
+ 'tiktokads://reporting': 'reporting-reference.md',
35
+ 'tiktokads://rate-limits': 'rate-limits-and-quotas.md'
36
+ };
37
+
38
+ export function getResourcesList() {
39
+ return RESOURCES;
40
+ }
41
+
42
+ /**
43
+ * Read a resource document by URI.
44
+ * @param {string} uri
45
+ * @returns {{ contents: Array<{ uri: string, mimeType: string, text: string }> }}
46
+ */
47
+ export function readResource(uri) {
48
+ const filename = RESOURCE_FILE_MAP[uri];
49
+ if (!filename) {
50
+ throw new Error(`Unknown resource: ${uri}. Available: ${Object.keys(RESOURCE_FILE_MAP).join(', ')}`);
51
+ }
52
+
53
+ const filePath = join(__dirname, filename);
54
+ const content = readFileSync(filePath, 'utf8');
55
+
56
+ return {
57
+ contents: [
58
+ {
59
+ uri,
60
+ mimeType: 'text/markdown',
61
+ text: content
62
+ }
63
+ ]
64
+ };
65
+ }
@@ -0,0 +1,39 @@
1
+ # TikTok Ads Rate Limits and Quotas
2
+
3
+ The TikTok Business API enforces per-app QPS (queries per second) limits. Limits vary by endpoint tier and the access level of your developer app; they are not published as fixed per-endpoint numbers.
4
+
5
+ ## Throttling Signals
6
+
7
+ - API error `code 40100` ("Too many requests") in the response envelope — usually still HTTP `200`
8
+ - HTTP `429` at the transport level
9
+
10
+ ## What This MCP Server Does
11
+
12
+ - Treats HTTP `429` and envelope error code `40100` as throttling signals.
13
+ - Reads the `Retry-After` response header when available.
14
+ - Falls back to `60` seconds if `Retry-After` is missing or invalid.
15
+ - Retries throttled requests once.
16
+
17
+ ## Backoff Behavior
18
+
19
+ `Retry-After` values may be:
20
+
21
+ - Integer seconds (for example `7`)
22
+ - HTTP date string
23
+
24
+ The server converts this to milliseconds and sleeps before a single retry.
25
+
26
+ ## Timeout Behavior
27
+
28
+ - Requests use a default timeout of `30000` ms.
29
+ - Override with `TIKTOK_ADS_REQUEST_TIMEOUT_MS` environment variable.
30
+ - Timeout errors are surfaced as request failures so callers can retry or degrade gracefully.
31
+
32
+ ## Operational Guidance
33
+
34
+ - Keep requested field, dimension, and metric sets narrow.
35
+ - Use `limit` deliberately and paginate only as needed (`page_size` max 1000).
36
+ - Batch id lookups: `/advertiser/info/` accepts up to 100 ids, status updates up to 100 ids per call.
37
+ - Avoid broad fan-out query loops across many advertisers at once.
38
+ - Cache read-heavy metadata (advertiser and entity lists) where possible.
39
+ - Stagger high-volume reporting jobs to reduce burst pressure.
@@ -0,0 +1,60 @@
1
+ # TikTok Ads Reporting Reference
2
+
3
+ The `report` tool wraps the synchronous integrated report endpoint:
4
+
5
+ `GET /report/integrated/get/`
6
+
7
+ ## Report Types
8
+
9
+ - `BASIC` (default): performance metrics by advertiser/campaign/adgroup/ad
10
+ - `AUDIENCE`: performance broken down by audience dimensions (age, gender, country, etc.)
11
+
12
+ The API also supports `PLAYABLE_MATERIAL`, `CATALOG`, and `BC` report types; this server intentionally exposes only `BASIC` and `AUDIENCE`.
13
+
14
+ ## Data Levels
15
+
16
+ `data_level` controls aggregation and must match the id dimension used:
17
+
18
+ | data_level | id dimension |
19
+ |------------|--------------|
20
+ | `AUCTION_ADVERTISER` | `advertiser_id` |
21
+ | `AUCTION_CAMPAIGN` | `campaign_id` |
22
+ | `AUCTION_ADGROUP` | `adgroup_id` |
23
+ | `AUCTION_AD` | `ad_id` |
24
+
25
+ ## Dimensions
26
+
27
+ - Id dimensions: `advertiser_id`, `campaign_id`, `adgroup_id`, `ad_id`
28
+ - Time dimensions: `stat_time_day`, `stat_time_hour` (not allowed with lifetime queries)
29
+ - Audience dimensions (`report_type: AUDIENCE`): `age`, `gender`, `country_code`, `language`, `platform`, `ac`
30
+
31
+ Default in this server: the data_level id dimension plus `stat_time_day` (id dimension only when `lifetime: true`).
32
+
33
+ ## Common BASIC Metrics
34
+
35
+ - Cost: `spend`, `cpc`, `cpm`
36
+ - Delivery: `impressions`, `clicks`, `ctr`, `reach`, `frequency`, `cost_per_1000_reached`
37
+ - Conversion: `conversion`, `cost_per_conversion`, `conversion_rate`, `real_time_conversion`
38
+ - On-platform result: `result`, `cost_per_result`, `result_rate`
39
+ - Video: `video_play_actions`, `video_watched_2s`, `video_watched_6s`, `average_video_play`
40
+ - Engagement: `likes`, `comments`, `shares`, `follows`, `profile_visits`
41
+
42
+ Note: the conversion metric is `conversion` (singular). Attribution metrics like `skan_*` exist for iOS campaigns.
43
+
44
+ ## Date Handling
45
+
46
+ - `start_date` / `end_date` in `YYYY-MM-DD`, interpreted in the advertiser account timezone
47
+ - Or `query_lifetime: true` for lifetime metrics (mutually exclusive with dates and `stat_time_*` dimensions)
48
+ - This server defaults to the trailing 7 days (UTC) when no dates are given
49
+
50
+ ## Filtering and Sorting
51
+
52
+ - `filtering`: JSON-encoded array of clauses, e.g.
53
+ `[{"field_name": "campaign_ids", "filter_type": "IN", "filter_value": "[\"123\"]"}]`
54
+ - `order_field` + `order_type` (`ASC` / `DESC`) sort results server-side
55
+
56
+ ## Pagination
57
+
58
+ - `page` (starts at 1) and `page_size` (max 1000)
59
+ - Responses include `data.page_info { page, page_size, total_number, total_page }`
60
+ - Rows arrive as `{ dimensions: {...}, metrics: {...} }`; this server flattens each row into a single object