Chat ai
/**
* campaign_workflow.js
* Template: create campaign -> upload video -> create adgroup -> create ad
*
* WARNING: Adapt fields to your account and API version. Test in sandbox before production.
*
* Requires:
* npm i node-fetch@2 form-data
*
* Env vars:
* TIKTOK_ACCESS_TOKEN - OAuth token with ad permissions
* ADVERTISER_ID - your advertiser id (string/number)
* VIDEO_FILE_PATH - optional local path to video (mp4)
* VIDEO_URL - optional: remote URL to pull from (preferred if available)
*
* Docs: TikTok Business API / File API / Adgroup create. See official docs for exact fields and versions.
* https://business-api.tiktok.com/portal/docs
* https://developers.tiktok.com/doc/content-posting-api-reference-upload-video
* /open_api/v1.3/adgroup/create/ etc.
* (Cite: TikTok Business API docs). 2
*/
const fetch = require('node-fetch');
const fs = require('fs');
const FormData = require('form-data');
const BASE = 'https://business-api.tiktok.com'; // base for many ad endpoints
const ACCESS_TOKEN = process.env.TIKTOK_ACCESS_TOKEN;
const ADVERTISER_ID = process.env.ADVERTISER_ID;
if (!ACCESS_TOKEN || !ADVERTISER_ID) {
console.error('Please set TIKTOK_ACCESS_TOKEN and ADVERTISER_ID env vars.');
process.exit(1);
}
async function apiPost(path, body, isForm = false) {
const url = `${BASE}${path}`;
const headers = {
Authorization: `Bearer ${ACCESS_TOKEN}`,
};
try {
const resp = await fetch(url, {
method: 'POST',
headers: isForm ? headers : { ...headers, 'Content-Type': 'application/json' },
body: isForm ? body : JSON.stringify(body),
});
const json = await resp.json();
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}: ${JSON.stringify(json)}`);
}
return json;
} catch (err) {
throw err;
}
}
/* 1) Create campaign */
async function createCampaign({ name = 'API Created Campaign', objective = 'TRAFFIC', budget = 1000, budget_mode = 'BUDGET_MODE_DAY' } = {}) {
const path = '/open_api/v1.3/campaign/create/';
const payload = {
advertiser_id: ADVERTISER_ID,
name,
objective, // e.g. TRAFFIC, REACH, CONVERSIONS - check allowed values
budget,
budget_mode, // BUDGET_MODE_DAY or BUDGET_MODE_TOTAL
};
console.log('Creating campaign...', payload);
const res = await apiPost(path, payload);
console.log('Campaign create response:', res);
// Response structure varies; extract campaign_id accordingly
// e.g. res.data.campaign_id or res.data ? Check your API version.
const campaignId = res?.data?.campaign_id || (res?.data && res.data[0] && res.data[0].campaign_id) || null;
if (!campaignId) throw new Error('Campaign ID not found in response');
return campaignId;
}
/* 2) Upload video to Asset Library (file endpoint) */
async function uploadVideo({ filePath, pullUrl } = {}) {
// Two common patterns:
// - File upload endpoint: /open_api/v1.3/file/video/ad/upload/
// - Content Posting API (if posting content for a user) as separate flow.
// We will use asset upload endpoint for ads.
const path = '/open_api/v1.3/file/video/ad/upload/';
if (pullUrl) {
// If API supports PULL_FROM_URL method, payload may be JSON with transfer_method:PULL_FROM_URL
console.log('Requesting server-side pull from URL (if supported).');
const payload = {
advertiser_id: ADVERTISER_ID,
video_url: pullUrl, // field names vary by API; check docs
transfer_method: 'PULL_FROM_URL',
};
const res = await apiPost(path, payload);
console.log('Video upload (pull) response:', res);
const videoId = res?.data?.video_id || res?.data?.video_list?.[0]?.video_id || null;
if (!videoId) throw new Error('Video ID not found in response for pull');
return videoId;
}
if (filePath) {
// multipart/form-data upload
console.log('Uploading local file:', filePath);
const form = new FormData();
form.append('advertiser_id', ADVERTISER_ID);
form.append('upload_type', 'UPLOAD_TYPE_SIMPLE'); // API may accept variants
form.append('video_file', fs.createReadStream(filePath));
// node-fetch v2 requires headers from form
const res = await apiPost(path, form, true);
console.log('Video upload (file) response:', res);
const videoId = res?.data?.video_id || res?.data?.video_list?.[0]?.video_id || null;
if (!videoId) throw new Error('Video ID not found in response for upload');
return videoId;
}
throw new Error('Either filePath or pullUrl must be provided for video upload');
}
/* 3) Create Ad Group (with geo targeting) */
async function createAdGroup({ campaignId, name = 'API AdGroup', budget = 500, countryList = ['BR'], startTimeSec = Math.floor(Date.now() / 1000), endTimeSec = Math.floor(Date.now() / 1000) + 7 * 24 * 3600 } = {}) {
const path = '/open_api/v1.3/adgroup/create/';
const payload = {
advertiser_id: ADVERTISER_ID,
campaign_id: campaignId,
name,
budget,
budget_mode: 'BUDGET_MODE_DAY',
schedule_type: 'SCHEDULE_TYPE_IMMEDIATE',
start_time: startTimeSec,
end_time: endTimeSec,
placement_type: 'PLACEMENT_TYPE_AUTO',
targeting: {
geo_location: {
country: countryList, // ISO2 codes array (ex: ['BR','US'])
},
age: { min: 18, max: 45 },
// add other dims if needed: gender, interests, device_type...
},
bid: 1.0,
optimization_goal: 'OPTIMIZATION_GOAL_IMPRESSIONS', // adjust per objective
};
console.log('Creating AdGroup...', payload);
const res = await apiPost(path, payload);
console.log('AdGroup create response:', res);
const adgroupId = res?.data?.adgroup_id || null;
if (!adgroupId) throw new Error('AdGroup ID not found');
return adgroupId;
}
/* 4) Create Ad (attach creative/video) */
async function createAd({ adgroupId, name = 'API Ad', videoId, landing_url = 'https://www.tiktok.com/@yourprofile' } = {}) {
const path = '/open_api/v1.3/ad/create/';
const payload = {
advertiser_id: ADVERTISER_ID,
adgroup_id: adgroupId,
name,
creative_material_mode: 'CREATIVE_MATERIAL_MODE_VIDEO',
// creative material info – fields vary by API version:
creative: {
video_id: videoId,
title: name,
// call_to_action, landing_page_url, external_url etc.
landing_url,
},
};
console.log('Creating Ad...', payload);
const res = await apiPost(path, payload);
console.log('Ad create response:', res);
const adId = res?.data?.ad_id || null;
if (!adId) throw new Error('Ad ID not found');
return adId;
}
/* Main flow */
(async () => {
try {
// 1) create campaign
const campaignId = await createCampaign({ name: 'My API Campaign', objective: 'TRAFFIC', budget: 1000 });
console.log('Campaign ID:', campaignId);
// 2) upload video (choose VIDEO_FILE_PATH or VIDEO_URL)
const filePath = process.env.VIDEO_FILE_PATH; // e.g., './videos/myclip.mp4'
const pullUrl = process.env.VIDEO_URL; // or a public URL if supported
const videoId = await uploadVideo({ filePath, pullUrl });
console.log('Video uploaded. video_id:', videoId);
// 3) create adgroup with geo targeting (Brazil + US as example)
const adgroupId = await createAdGroup({ campaignId, name: 'Geo BR+US', countryList: ['BR', 'US'], budget: 500 });
console.log('Adgroup ID:', adgroupId);
// 4) create ad referencing video
const adId = await createAd({ adgroupId, name: 'My Video Ad', videoId, landing_url: 'https://www.tiktok.com/@ichardy0' });
console.log('Ad created. ad_id:', adId);
console.log('Workflow finished successfully.');
} catch (err) {
console.error('Workflow error:', err);
process.exit(1);
}
})();
关闭于 2025-11-10 5 条评论