[PHASE 3] TikTok Ads Data Source
Phase
Phase 3 — Data Source Expansion | Depends on: Issues 01, 04. Can be built in parallel with Issues 09 and 10.
Description
TikTok Ads has become a primary performance marketing channel for B2C brands targeting audiences aged 18-40. Many marketing teams running campaigns across Google, Meta, and LinkedIn also run substantial budgets on TikTok, particularly for video-led awareness and retargeting campaigns. Without TikTok data, the Marketing Hub's channel comparison table (Issue 06) has a visible gap, and CPL/ROAS benchmarking across channels is incomplete.
This issue adds TikTok Ads as a fourth digital advertising data source, following the same architectural pattern established for Google Ads, Meta Ads, and LinkedIn Ads. The connector uses the TikTok Marketing API via OAuth 2.0 with advertiser account selection.
What currently exists (do NOT rebuild)
| Layer |
File |
Status |
| Meta Ads OAuth pattern |
backend/src/services/MetaAdsService.ts |
✅ Pattern to follow for TikTok OAuth |
dra_meta_ads schema |
Existing tables |
✅ Schema structure to mirror |
DataSourceProcessor sync dispatch |
backend/src/processors/DataSourceProcessor.ts |
✅ Extend |
| Marketing Hub channel normalisation |
backend/src/processors/MarketingReportProcessor.ts (Issue 06) |
✅ Add TikTok channel |
| Classification system |
Issue 04 |
✅ Auto-classified as marketing_campaign_data |
First Principles Rationale
Completeness of the channel comparison view is a key value driver for the Marketing Hub. A CMO managing £500K/month in ad spend across four platforms who cannot see TikTok in the same table as Google and Meta must do external reconciliation — which is exactly the problem this platform solves. TikTok's Marketing API is well-documented and follows standard OAuth + reports pattern. The implementation effort is proportional to the existing ad platform connectors making this a logical inclusion in Phase 3.
Acceptance Criteria
Technical Implementation Plan
1. EDataSourceType Extension
// backend/src/types/EDataSourceType.ts
export enum EDataSourceType {
// ...existing values...
TIKTOK_ADS = 'tiktok_ads'
}
Migration: Add 'tiktok_ads' to dra_data_sources_data_type_enum PostgreSQL enum.
2. TikTok Ads Database Schema (dra_tiktok_ads)
Migration [timestamp]-CreateTikTokAdsSchema.ts:
CREATE SCHEMA IF NOT EXISTS dra_tiktok_ads;
CREATE TABLE dra_tiktok_ads.advertiser_info (
id SERIAL PRIMARY KEY,
data_source_id INTEGER NOT NULL REFERENCES dra_data_sources(id) ON DELETE CASCADE,
advertiser_id VARCHAR(50) NOT NULL,
advertiser_name VARCHAR(512),
currency VARCHAR(10),
timezone VARCHAR(50),
synced_at TIMESTAMP
);
CREATE TABLE dra_tiktok_ads.campaign_performance (
id SERIAL PRIMARY KEY,
data_source_id INTEGER NOT NULL REFERENCES dra_data_sources(id) ON DELETE CASCADE,
stat_date DATE NOT NULL,
campaign_id VARCHAR(50) NOT NULL,
campaign_name VARCHAR(512),
objective_type VARCHAR(100), -- 'REACH' | 'VIDEO_VIEWS' | 'CONVERSIONS' | 'APP_INSTALL' etc.
status VARCHAR(50),
spend NUMERIC(12,2) DEFAULT 0,
impressions BIGINT DEFAULT 0,
clicks INTEGER DEFAULT 0,
ctr NUMERIC(8,4) DEFAULT 0, -- click-through rate
cpm NUMERIC(10,4) DEFAULT 0, -- cost per mille impressions
cpc NUMERIC(10,4) DEFAULT 0, -- cost per click
conversions INTEGER DEFAULT 0,
cost_per_conversion NUMERIC(10,2) DEFAULT 0,
conversion_rate NUMERIC(8,4) DEFAULT 0,
video_views BIGINT DEFAULT 0, -- TikTok-specific
video_play_actions BIGINT DEFAULT 0, -- 6-second views
reach BIGINT DEFAULT 0,
UNIQUE(data_source_id, stat_date, campaign_id)
);
CREATE TABLE dra_tiktok_ads.ad_group_performance (
id SERIAL PRIMARY KEY,
data_source_id INTEGER NOT NULL REFERENCES dra_data_sources(id) ON DELETE CASCADE,
stat_date DATE NOT NULL,
campaign_id VARCHAR(50) NOT NULL,
ad_group_id VARCHAR(50) NOT NULL,
ad_group_name VARCHAR(512),
spend NUMERIC(12,2) DEFAULT 0,
impressions BIGINT DEFAULT 0,
clicks INTEGER DEFAULT 0,
conversions INTEGER DEFAULT 0,
cpl NUMERIC(10,2) DEFAULT 0,
UNIQUE(data_source_id, stat_date, ad_group_id)
);
3. TikTokAdsService (backend/src/services/TikTokAdsService.ts)
export class TikTokAdsService {
private static instance: TikTokAdsService;
public static getInstance(): TikTokAdsService { ... }
private readonly BASE_URL = 'https://business-api.tiktok.com/open_api/v1.3';
// List advertiser accounts for the authenticated user
async listAdvertiserAccounts(accessToken: string): Promise<Array<{ id: string; name: string; currency: string }>>
// Sync campaign performance data
async syncCampaignPerformance(
dataSourceId: number,
advertiserId: string,
accessToken: string,
startDate: string, // YYYY-MM-DD
endDate: string
): Promise<void>
// Sync ad group performance data
async syncAdGroupPerformance(
dataSourceId: number,
advertiserId: string,
accessToken: string,
startDate: string,
endDate: string
): Promise<void>
// Refresh access token (TikTok tokens expire after 24 hours)
async refreshAccessToken(refreshToken: string): Promise<{ access_token: string; expires_in: number }>
}
TikTok Reporting API call pattern:
const response = await fetch(`${this.BASE_URL}/report/integrated/get/`, {
method: 'POST',
headers: {
'Access-Token': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
advertiser_id: advertiserId,
report_type: 'BASIC',
dimensions: ['campaign_id', 'stat_time_day'],
metrics: ['spend', 'impressions', 'clicks', 'ctr', 'cpm', 'cpc',
'conversions', 'cost_per_conversion', 'conversion_rate',
'video_play_actions', 'reach'],
start_date: startDate,
end_date: endDate,
page_size: 1000
})
});
4. OAuth Flow
TikTok Marketing API OAuth 2.0:
Authorization URL:
https://business-api.tiktok.com/portal/auth?app_id=[APP_ID]&state=[STATE]&redirect_uri=[URI]&scope=reporting,campaign_management
Token exchange: POST https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/
Token storage: follows the existing encrypted connection_details pattern. Store access_token, refresh_token, advertiser_id, expires_at.
Environment variables needed:
TIKTOK_ADS_APP_ID=...
TIKTOK_ADS_APP_SECRET=...
TIKTOK_ADS_REDIRECT_URI=...
5. Backend OAuth Route
Add backend/src/routes/oauth/tiktok.ts following the pattern of backend/src/routes/oauth/google.ts and backend/src/routes/oauth/linkedin.ts.
GET /oauth/tiktok — initiate OAuth redirect
GET /oauth/tiktok/callback — handle code exchange + save tokens
6. DataSourceProcessor Extension
Add to the sync dispatch:
case EDataSourceType.TIKTOK_ADS:
await tikTokService.syncCampaignPerformance(dataSourceId, advertiserId, accessToken, startDate, endDate);
await tikTokService.syncAdGroupPerformance(dataSourceId, advertiserId, accessToken, startDate, endDate);
break;
7. MarketingReportProcessor Integration (Issue 06)
Add TikTok to getChannelMetrics():
// Query dra_tiktok_ads.campaign_performance for the date range
const tikTok: IChannelMetrics = {
channelType: 'tiktok_ads',
channelLabel: 'TikTok Ads',
spend: sum(spend),
impressions: sum(impressions),
clicks: sum(clicks),
ctr: total_clicks / total_impressions,
conversions: sum(conversions),
cpl: total_spend / total_conversions,
roas: 0, // TikTok does not report revenue unless conversion tracking is configured
pipelineValue: 0,
dataSourceId: dataSource.id
};
8. Frontend Composable (frontend/composables/useTikTokAds.ts)
Following the pattern of useGoogleAds.ts / useLinkedInAds.ts:
- Initiate OAuth: redirect to
/oauth/tiktok
- After callback, fetch advertiser accounts and present selector
- Save selected advertiser to data source
Files to Create
| File |
Purpose |
backend/src/services/TikTokAdsService.ts |
TikTok Marketing API sync service |
backend/src/routes/oauth/tiktok.ts |
OAuth initiate + callback routes |
backend/src/migrations/[timestamp]-AddTikTokAdsDataSourceType.ts |
Extend enum |
backend/src/migrations/[timestamp]-CreateTikTokAdsSchema.ts |
dra_tiktok_ads tables |
frontend/composables/useTikTokAds.ts |
Frontend OAuth + data composable |
Files to Modify
| File |
Change |
backend/src/types/EDataSourceType.ts |
Add TIKTOK_ADS = 'tiktok_ads' |
backend/src/processors/DataSourceProcessor.ts |
Dispatch TikTok sync |
backend/src/processors/MarketingReportProcessor.ts |
Add TikTok channel row |
backend/src/server.ts |
Register TikTok OAuth routes |
frontend/utils/dataSourceClassifications.ts (Issue 04) |
Add 'tiktok_ads' to AUTO_CLASSIFIED_SOURCE_TYPES |
| Frontend data source connect UI |
Add TikTok Ads connector card |
docker/backend/ env config |
Add TikTok env variables |
Required Environment Variables
TIKTOK_ADS_APP_ID=
TIKTOK_ADS_APP_SECRET=
TIKTOK_ADS_REDIRECT_URI=
Add to .env.example and docker-compose.yml backend service environment section.
Dependencies
- Requires: Issue 01 (Navigation), Issue 04 (Classification — auto-classified)
- Enhances: Issue 06 (Marketing Hub adds TikTok as a fourth paid channel row)
- Can be built in parallel with Issues 09 and 10
Testing Requirements
[PHASE 3] TikTok Ads Data Source
Phase
Phase 3 — Data Source Expansion | Depends on: Issues 01, 04. Can be built in parallel with Issues 09 and 10.
Description
TikTok Ads has become a primary performance marketing channel for B2C brands targeting audiences aged 18-40. Many marketing teams running campaigns across Google, Meta, and LinkedIn also run substantial budgets on TikTok, particularly for video-led awareness and retargeting campaigns. Without TikTok data, the Marketing Hub's channel comparison table (Issue 06) has a visible gap, and CPL/ROAS benchmarking across channels is incomplete.
This issue adds TikTok Ads as a fourth digital advertising data source, following the same architectural pattern established for Google Ads, Meta Ads, and LinkedIn Ads. The connector uses the TikTok Marketing API via OAuth 2.0 with advertiser account selection.
What currently exists (do NOT rebuild)
backend/src/services/MetaAdsService.tsdra_meta_adsschemaDataSourceProcessorsync dispatchbackend/src/processors/DataSourceProcessor.tsbackend/src/processors/MarketingReportProcessor.ts(Issue 06)marketing_campaign_dataFirst Principles Rationale
Completeness of the channel comparison view is a key value driver for the Marketing Hub. A CMO managing £500K/month in ad spend across four platforms who cannot see TikTok in the same table as Google and Meta must do external reconciliation — which is exactly the problem this platform solves. TikTok's Marketing API is well-documented and follows standard OAuth + reports pattern. The implementation effort is proportional to the existing ad platform connectors making this a logical inclusion in Phase 3.
Acceptance Criteria
tiktok_adsis a selectable data source type in the "Connect Data Source" flow with TikTok logo and description/open_api/v1.3/oauth2/advertiser/get/)/open_api/v1.3/report/integrated/get/)dra_tiktok_adsschemaSchedulerServicepatternmarketing_campaign_data— no classification modal shownnpm run validate:ssrpassesTechnical Implementation Plan
1.
EDataSourceTypeExtensionMigration: Add
'tiktok_ads'todra_data_sources_data_type_enumPostgreSQL enum.2. TikTok Ads Database Schema (
dra_tiktok_ads)Migration
[timestamp]-CreateTikTokAdsSchema.ts:3.
TikTokAdsService(backend/src/services/TikTokAdsService.ts)TikTok Reporting API call pattern:
4. OAuth Flow
TikTok Marketing API OAuth 2.0:
Authorization URL:
Token exchange:
POST https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/Token storage: follows the existing encrypted
connection_detailspattern. Storeaccess_token,refresh_token,advertiser_id,expires_at.Environment variables needed:
5. Backend OAuth Route
Add
backend/src/routes/oauth/tiktok.tsfollowing the pattern ofbackend/src/routes/oauth/google.tsandbackend/src/routes/oauth/linkedin.ts.6.
DataSourceProcessorExtensionAdd to the sync dispatch:
7.
MarketingReportProcessorIntegration (Issue 06)Add TikTok to
getChannelMetrics():8. Frontend Composable (
frontend/composables/useTikTokAds.ts)Following the pattern of
useGoogleAds.ts/useLinkedInAds.ts:/oauth/tiktokFiles to Create
backend/src/services/TikTokAdsService.tsbackend/src/routes/oauth/tiktok.tsbackend/src/migrations/[timestamp]-AddTikTokAdsDataSourceType.tsbackend/src/migrations/[timestamp]-CreateTikTokAdsSchema.tsfrontend/composables/useTikTokAds.tsFiles to Modify
backend/src/types/EDataSourceType.tsTIKTOK_ADS = 'tiktok_ads'backend/src/processors/DataSourceProcessor.tsbackend/src/processors/MarketingReportProcessor.tsbackend/src/server.tsfrontend/utils/dataSourceClassifications.ts(Issue 04)'tiktok_ads'toAUTO_CLASSIFIED_SOURCE_TYPESdocker/backend/env configRequired Environment Variables
Add to
.env.exampleanddocker-compose.ymlbackend service environment section.Dependencies
Testing Requirements
listAdvertiserAccounts()returns accounts from the TikTok Business API with correct tokensyncCampaignPerformance()handles TikTok's pagination (page_info.pagecursor)expires_at) check triggers refresh before API call(data_source_id, stat_date, campaign_id)prevents duplicate rows on syncdra_data_sourcesrecord cascades to alldra_tiktok_adstablesvideo_play_actions,reach) are stored even though they have no equivalent in other platformsnpm run validate:ssrpasses