Reporting¶
The Reporting endpoint returns aggregated performance metrics — spend, impressions, clicks, installs, purchases — for an advertiser account at the level of detail you choose: whole account, campaign, ad group or ad, optionally broken down by day and by country. This is the endpoint you call every day to collect ad performance.
Verify against Ads Manager
The same report is available on screen at ads-manager.huydata.com with the platform's own column names; its Totals row is the reference for your pipeline. See Ads Manager.
Get integrated report¶
GET /open_api/v1.3/report/integrated/get/
Parameters¶
| Name | Type | Required | Description |
|---|---|---|---|
advertiser_id |
string | Yes | Advertiser account id. One account per call. |
report_type |
string | Yes | Must be BASIC. |
service_type |
string | No | AUCTION (default and only value). |
data_level |
string | Yes | Aggregation level: AUCTION_ADVERTISER, AUCTION_CAMPAIGN, AUCTION_ADGROUP or AUCTION_AD. |
dimensions |
JSON array of strings | Yes | Grouping keys. Must contain the id dimension that matches data_level (advertiser_id, campaign_id, adgroup_id or ad_id). May additionally contain stat_time_day (one row per day) and/or country_code (one row per country). No other dimension is accepted. |
metrics |
JSON array of strings | No | Metrics to return, see Metrics. Default: ["spend","impressions","clicks","conversion"]. |
start_date |
string YYYY-MM-DD |
Yes, unless lifetime=true |
First report date, inclusive. |
end_date |
string YYYY-MM-DD |
Yes, unless lifetime=true |
Last report date, inclusive. Must be on or after start_date. |
lifetime |
boolean (true/false) |
No | true returns the whole available history (2026-06-30 to the latest available date) and ignores start_date/end_date. Default false. |
filtering |
JSON array of objects | No | Row filters, see Filtering. Note the different shape from the campaign/adgroup/ad endpoints. |
order_field |
string | No | Metric to sort by: any numeric metric from the table below, or stat_time_day. A metric used here must also be listed in metrics, otherwise the call fails with 40001. Attribute metrics such as campaign_name cannot be used. |
order_type |
string | No | DESC (default) or ASC. Any other value fails with 40001. |
page |
integer | No | Page number, starting at 1. Default 1. |
page_size |
integer | No | Rows per page, 1–1000. Default 10. Use 1000 for daily pulls. |
Dimensions¶
data_level |
Required id dimension | Optional dimensions |
|---|---|---|
AUCTION_ADVERTISER |
advertiser_id |
stat_time_day, country_code |
AUCTION_CAMPAIGN |
campaign_id |
stat_time_day, country_code |
AUCTION_ADGROUP |
adgroup_id |
stat_time_day, country_code |
AUCTION_AD |
ad_id |
stat_time_day, country_code |
- Without
stat_time_day, metrics are summed over the whole date range (one row per entity, or per entity and country). stat_time_dayis returned as"YYYY-MM-DD 00:00:00"— a UTC day with a zero time component. Take the first 10 characters to get the date.country_codeis the ISO 3166-1 alpha-2 code (US,JP,VN, ...). The same codes are returned asregion_codeby/tool/region/get/.- Rows are sorted by
stat_time_day, then id, thencountry_codeunlessorder_fieldis given. - Rows with no delivery are not returned. If an ad did not deliver in a country on a day, there is no row with zeros — the row is absent. The number of rows therefore varies from day to day.
Metrics¶
All metric values are returned as strings, including counts and money. Convert them in your client (int() for counts, Decimal/float for the others).
Numeric metrics:
| Metric | Unit | Description |
|---|---|---|
spend |
USD, 2 decimals | Amount spent. |
impressions |
count | Ad impressions. |
clicks |
count | Clicks. |
ctr |
percent, 2 decimals | clicks / impressions × 100. |
cpm |
USD | Cost per 1,000 impressions. |
cpc |
USD | Cost per click. |
reach |
count | Unique users reached. Summed across the rows of the group, so it is not de-duplicated across days or countries. |
frequency |
ratio, 2 decimals | impressions / reach. |
conversion |
count | App installs attributed to the ad — the optimization event of these accounts. Identical to app_install. |
cost_per_conversion |
USD | spend / conversion. |
conversion_rate |
percent | conversion / clicks × 100. |
app_install |
count | Same as conversion. |
cost_per_app_install |
USD | Same as cost_per_conversion. |
purchase |
count | In-app purchase events attributed to the ad. |
total_purchase_value |
USD, 2 decimals | Revenue of attributed purchases. |
video_play_actions |
count | Video plays (video and playable ads). 0 for image ads. |
video_views_p100 |
count | Plays that reached 100 % of the video. |
Ratios are computed after aggregation and return "0.00" when the denominator is zero.
Attribute ("name/id") metrics — returned as strings in the same metrics object, useful to avoid a join with the entity endpoints:
| Metric | Available at | Description |
|---|---|---|
campaign_id |
campaign, ad group, ad | Parent campaign id. |
campaign_name |
campaign, ad group, ad | Parent campaign name. |
adgroup_id |
ad group, ad | Parent ad group id. |
adgroup_name |
ad group, ad | Parent ad group name. |
ad_name |
ad | Ad name. |
currency |
all levels | USD. |
objective_type |
all levels | APP_PROMOTION. |
Requesting an attribute metric at a level where it is not available fails with 40001 (for example Name/id metrics are not available at AUCTION_ADVERTISER level.).
Filtering¶
filtering is a JSON array of rule objects (a different shape from the filtering object used by /campaign/get/, /adgroup/get/ and /ad/get/):
[{"field_name": "campaign_ids", "filter_type": "IN", "filter_value": "[\"1871286220402645533\"]"}]
| Key | Type | Description |
|---|---|---|
field_name |
string | campaign_ids, adgroup_ids, ad_ids or country_code. |
filter_type |
string | IN (default) or NOT_IN. |
filter_value |
string | A JSON-encoded array, as a string — the list is serialized a second time inside the outer JSON. A bare string ("JP") is accepted as a single value. |
Rules are combined with AND. Filtering happens before aggregation, so filtering by country_code without the country_code dimension gives totals for the selected countries only.
Request: ad level, per day and country¶
curl -s -G "https://business-api.huydata.com/open_api/v1.3/report/integrated/get/" \
-H "Access-Token: $TIKTOK_ACCESS_TOKEN" \
--data-urlencode "advertiser_id=7643153585721644371" \
--data-urlencode "report_type=BASIC" \
--data-urlencode "data_level=AUCTION_AD" \
--data-urlencode 'dimensions=["ad_id","stat_time_day","country_code"]' \
--data-urlencode 'metrics=["campaign_id","campaign_name","adgroup_id","adgroup_name","ad_name","currency","objective_type","spend","impressions","clicks","ctr","cpm","cpc","reach","frequency","conversion","cost_per_conversion","conversion_rate","app_install","cost_per_app_install","purchase","total_purchase_value","video_play_actions","video_views_p100"]' \
--data-urlencode "start_date=2026-10-27" \
--data-urlencode "end_date=2026-10-29" \
--data-urlencode 'filtering=[{"field_name":"ad_ids","filter_type":"IN","filter_value":"[\"1831381570943309792\"]"}]' \
--data-urlencode "page_size=2"
Response¶
{
"code": 0,
"message": "OK",
"request_id": "1f260e2ffae041bda805",
"data": {
"list": [
{
"dimensions": {
"ad_id": "1831381570943309792",
"stat_time_day": "2026-10-27 00:00:00",
"country_code": "JP"
},
"metrics": {
"campaign_id": "1871286220402645533",
"campaign_name": "DMS_TT_AND_EA_INSTALL_202602",
"adgroup_id": "1868805261510613175",
"adgroup_name": "EA_Broad_JP-KR-TW",
"ad_name": "VID_Tutorial_v1",
"currency": "USD",
"objective_type": "APP_PROMOTION",
"spend": "11.56",
"impressions": "1221",
"clicks": "7",
"ctr": "0.57",
"cpm": "9.47",
"cpc": "1.65",
"reach": "739",
"frequency": "1.65",
"conversion": "1",
"cost_per_conversion": "11.56",
"conversion_rate": "14.29",
"app_install": "1",
"cost_per_app_install": "11.56",
"purchase": "0",
"total_purchase_value": "0.00",
"video_play_actions": "631",
"video_views_p100": "113"
}
},
{
"dimensions": {
"ad_id": "1831381570943309792",
"stat_time_day": "2026-10-27 00:00:00",
"country_code": "KR"
},
"metrics": {
"campaign_id": "1871286220402645533",
"campaign_name": "DMS_TT_AND_EA_INSTALL_202602",
"adgroup_id": "1868805261510613175",
"adgroup_name": "EA_Broad_JP-KR-TW",
"ad_name": "VID_Tutorial_v1",
"currency": "USD",
"objective_type": "APP_PROMOTION",
"spend": "6.59",
"impressions": "850",
"clicks": "5",
"ctr": "0.59",
"cpm": "7.75",
"cpc": "1.32",
"reach": "542",
"frequency": "1.57",
"conversion": "0",
"cost_per_conversion": "0.00",
"conversion_rate": "0.00",
"app_install": "0",
"cost_per_app_install": "0.00",
"purchase": "0",
"total_purchase_value": "0.00",
"video_play_actions": "490",
"video_views_p100": "88"
}
}
],
"page_info": {"page": 1, "page_size": 2, "total_number": 9, "total_page": 5}
}
}
Each row has two objects: dimensions (the grouping keys you asked for) and metrics (everything in metrics, all strings).
Request: account totals per day, default metrics¶
curl -s -G "https://business-api.huydata.com/open_api/v1.3/report/integrated/get/" \
-H "Access-Token: $TIKTOK_ACCESS_TOKEN" \
--data-urlencode "advertiser_id=7643153585721644371" \
--data-urlencode "report_type=BASIC" \
--data-urlencode "data_level=AUCTION_ADVERTISER" \
--data-urlencode 'dimensions=["advertiser_id","stat_time_day"]' \
--data-urlencode "start_date=2026-10-27" \
--data-urlencode "end_date=2026-10-29"
{
"code": 0,
"message": "OK",
"request_id": "9ebdbbf6838e4f1a98ed",
"data": {
"list": [
{"dimensions": {"advertiser_id": "7643153585721644371", "stat_time_day": "2026-10-27 00:00:00"}, "metrics": {"spend": "1216.74", "impressions": "239539", "clicks": "1510", "conversion": "296"}},
{"dimensions": {"advertiser_id": "7643153585721644371", "stat_time_day": "2026-10-28 00:00:00"}, "metrics": {"spend": "1385.12", "impressions": "278177", "clicks": "1673", "conversion": "329"}},
{"dimensions": {"advertiser_id": "7643153585721644371", "stat_time_day": "2026-10-29 00:00:00"}, "metrics": {"spend": "1433.99", "impressions": "271178", "clicks": "1599", "conversion": "271"}}
],
"page_info": {"page": 1, "page_size": 10, "total_number": 3, "total_page": 1}
}
}
Request: campaign lifetime totals, sorted by spend¶
curl -s -G "https://business-api.huydata.com/open_api/v1.3/report/integrated/get/" \
-H "Access-Token: $TIKTOK_ACCESS_TOKEN" \
--data-urlencode "advertiser_id=7643153585721644371" \
--data-urlencode "report_type=BASIC" \
--data-urlencode "data_level=AUCTION_CAMPAIGN" \
--data-urlencode 'dimensions=["campaign_id"]' \
--data-urlencode 'metrics=["campaign_name","objective_type","spend","impressions","clicks","conversion","cost_per_conversion","purchase","total_purchase_value"]' \
--data-urlencode "lifetime=true" \
--data-urlencode "order_field=spend" \
--data-urlencode "order_type=DESC"
{
"code": 0,
"message": "OK",
"request_id": "31db6f15f6734da4923d",
"data": {
"list": [
{
"dimensions": {"campaign_id": "1841269875139264223"},
"metrics": {"campaign_name": "DMS_TT_AND_T1_INSTALL_202609", "objective_type": "APP_PROMOTION", "spend": "49229.02", "impressions": "7481258", "clicks": "50803", "conversion": "7817", "cost_per_conversion": "6.30", "purchase": "422", "total_purchase_value": "12560.35"}
},
{
"dimensions": {"campaign_id": "1871286220402645533"},
"metrics": {"campaign_name": "DMS_TT_AND_EA_INSTALL_202602", "objective_type": "APP_PROMOTION", "spend": "45272.34", "impressions": "6046186", "clicks": "30996", "conversion": "4411", "cost_per_conversion": "10.26", "purchase": "343", "total_purchase_value": "12254.40"}
},
{
"dimensions": {"campaign_id": "1862348092944652029"},
"metrics": {"campaign_name": "DMS_TT_AND_SEA_INSTALL_202604", "objective_type": "APP_PROMOTION", "spend": "14400.52", "impressions": "10931272", "clicks": "66969", "conversion": "19714", "cost_per_conversion": "0.73", "purchase": "268", "total_purchase_value": "4012.65"}
}
],
"page_info": {"page": 1, "page_size": 10, "total_number": 3, "total_page": 1}
}
}
Request: ad groups of one campaign, two countries, one day¶
curl -s -G "https://business-api.huydata.com/open_api/v1.3/report/integrated/get/" \
-H "Access-Token: $TIKTOK_ACCESS_TOKEN" \
--data-urlencode "advertiser_id=7643153585721644371" \
--data-urlencode "report_type=BASIC" \
--data-urlencode "data_level=AUCTION_ADGROUP" \
--data-urlencode 'dimensions=["adgroup_id","stat_time_day","country_code"]' \
--data-urlencode 'metrics=["adgroup_name","campaign_id","spend","impressions","clicks","conversion"]' \
--data-urlencode "start_date=2026-10-29" \
--data-urlencode "end_date=2026-10-29" \
--data-urlencode 'filtering=[{"field_name":"campaign_ids","filter_type":"IN","filter_value":"[\"1871286220402645533\"]"},{"field_name":"country_code","filter_type":"IN","filter_value":"[\"JP\",\"KR\"]"}]' \
--data-urlencode "page_size=3"
{
"code": 0,
"message": "OK",
"request_id": "b8f51f9932ba4761ad64",
"data": {
"list": [
{"dimensions": {"adgroup_id": "1822048926325183510", "stat_time_day": "2026-10-29 00:00:00", "country_code": "JP"}, "metrics": {"adgroup_name": "EA_Lookalike_Payers_JP-KR-TW", "campaign_id": "1871286220402645533", "spend": "33.12", "impressions": "4158", "clicks": "24", "conversion": "4"}},
{"dimensions": {"adgroup_id": "1822048926325183510", "stat_time_day": "2026-10-29 00:00:00", "country_code": "KR"}, "metrics": {"adgroup_name": "EA_Lookalike_Payers_JP-KR-TW", "campaign_id": "1871286220402645533", "spend": "24.70", "impressions": "3265", "clicks": "17", "conversion": "2"}},
{"dimensions": {"adgroup_id": "1836683088316451285", "stat_time_day": "2026-10-29 00:00:00", "country_code": "JP"}, "metrics": {"adgroup_name": "EA_Interest_Gaming_JP-KR", "campaign_id": "1871286220402645533", "spend": "75.07", "impressions": "9590", "clicks": "47", "conversion": "6"}}
],
"page_info": {"page": 1, "page_size": 3, "total_number": 6, "total_page": 2}
}
}
Paging¶
A day of ad-level data split by country for one account is typically 10–100 rows, so page_size=1000 returns everything in one page. For longer date ranges, loop until page >= total_page:
import json
import os
import requests
BASE = "https://business-api.huydata.com/open_api/v1.3"
HEADERS = {"Access-Token": os.environ["TIKTOK_ACCESS_TOKEN"]}
def fetch_report(advertiser_id: str, start_date: str, end_date: str) -> list[dict]:
params = {
"advertiser_id": advertiser_id,
"report_type": "BASIC",
"data_level": "AUCTION_AD",
"dimensions": json.dumps(["ad_id", "stat_time_day", "country_code"]),
"metrics": json.dumps(["campaign_id", "campaign_name", "adgroup_id", "adgroup_name", "ad_name",
"spend", "impressions", "clicks", "conversion", "purchase", "total_purchase_value"]),
"start_date": start_date,
"end_date": end_date,
"page_size": 1000,
"page": 1,
}
rows = []
while True:
body = requests.get(f"{BASE}/report/integrated/get/", headers=HEADERS, params=params, timeout=60).json()
if body["code"] != 0:
raise RuntimeError(f"{body['code']}: {body['message']} (request_id={body['request_id']})")
data = body["data"]
rows.extend(data["list"])
if data["page_info"]["page"] >= data["page_info"]["total_page"]:
return rows
params["page"] += 1
total_number is the total number of rows across all pages, total_page the number of pages (always at least 1, even when list is empty). Requesting a page beyond total_page returns an empty list, not an error.
Data freshness and the attribution window¶
Availability¶
Report dates are UTC days. The data for date D becomes available at 00:10 UTC on D + 1. The latest available report date is therefore always yesterday (UTC). A date range that extends past yesterday is silently clipped: the unavailable days simply produce no rows and code is still 0.
{"code": 0, "message": "OK", "request_id": "df023713e87a45d98dc5", "data": {"list": [], "page_info": {"page": 1, "page_size": 10, "total_number": 0, "total_page": 1}}}
The earliest report date is 2026-06-30. Dates before it are clipped the same way.
Attribution window: 3 days¶
Spend, impressions, clicks and video metrics are final as soon as a date becomes available. Installs (conversion/app_install), purchases and purchase value are not. TikTok attributes installs and purchases to the ad for up to 3 days after the impression, so the values for a report date keep increasing until 3 days after that date, when they become final.
The same request — account totals for report date 2026-10-27 — issued on four different days:
| Pulled on (UTC) | Age of the date | spend |
impressions |
clicks |
conversion |
purchase |
total_purchase_value |
|---|---|---|---|---|---|---|---|
| 2026-10-28 | D+1 | 1216.74 | 239539 | 1510 | 267 | 11 | 236.09 |
| 2026-10-29 | D+2 | 1216.74 | 239539 | 1510 | 286 | 11 | 280.36 |
| 2026-10-30 | D+3 | 1216.74 | 239539 | 1510 | 296 | 12 | 326.18 |
| 2026-11-05 | later | 1216.74 | 239539 | 1510 | 296 | 12 | 326.18 |
Roughly 90 % of the final installs are visible on D+1, 97 % on D+2 and 100 % from D+3. Purchases and purchase value mature more slowly (about 80 % of the value on D+1 and 95 % on D+2); small purchase counts can stay flat for a day and then step up, as the purchase column above shows. Because counts are whole numbers, a row with few installs may move by exactly 1 between two reads, or not at all.
What this means for a daily pull:
- Each day after 00:10 UTC, pull yesterday (
D-1) — it is new. - Also re-pull the three most recent report dates (
D-1,D-2,D-3) and replace what you stored earlier for those dates. Those are the dates whoseconversion,app_install,cost_per_conversion,conversion_rate,purchaseandtotal_purchase_valuemay still have changed. - Dates older than
D-3do not change; there is no need to pull them again.
Because the API reports the current state of each date, there is no parameter to request "the values as of yesterday"; store the pull date with your rows if you need to reconstruct what was visible when.
Errors specific to Reporting¶
All are returned with HTTP 200 and code 40001:
| Situation | message |
|---|---|
report_type missing or not BASIC |
report_type is required and must be BASIC in this sandbox. |
data_level missing or misspelled |
data_level must be one of AUCTION_ADVERTISER, AUCTION_CAMPAIGN, AUCTION_ADGROUP, AUCTION_AD. |
dimensions missing |
Parameter 'dimensions' is required. |
| id dimension missing | dimensions must include ad_id when data_level=AUCTION_AD. |
| unknown dimension | Unsupported dimension 'age'. Supported: ad_id, stat_time_day, country_code. |
unknown metric (e.g. installs) |
Unsupported metric 'installs'. |
| attribute metric at the wrong level | Name/id metrics are not available at AUCTION_ADVERTISER level. / adgroup/ad metrics are not available at AUCTION_CAMPAIGN level. / ad_name is not available at AUCTION_ADGROUP level. |
no dates and no lifetime=true |
start_date and end_date are required unless lifetime=true. |
| wrong date format | start_date and end_date must use the format YYYY-MM-DD. |
end_date before start_date |
end_date must be on or after start_date. |
filtering not a list of objects |
filtering must be a JSON list of {field_name, filter_type, filter_value}. |
unknown field_name |
Unsupported filter field_name 'adgroup_id'. Supported: campaign_ids, adgroup_ids, ad_ids, country_code |
unknown filter_type |
Unsupported filter_type 'EQ'. Use IN or NOT_IN. |
order_field that is not a numeric metric |
order_field 'campaign_name' is not a numeric metric. |
order_field metric missing from metrics |
order_field 'spend' must also be included in metrics. |
order_type other than ASC/DESC |
order_type must be ASC or DESC. |
Example:
{
"code": 40001,
"message": "dimensions must include ad_id when data_level=AUCTION_AD.",
"request_id": "ccd5fbd11e534ab59400",
"data": {}
}