ITADN

Chat ai

#5956Closedricharddoratiotofy-del 创建于 2025-11-10
/** * 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 条评论