import crypto from 'crypto'; import https from 'https'; import fs from 'fs'; const envContent = fs.readFileSync('.env.local', 'utf8'); const env = {}; envContent.split('\n').forEach(line => { const match = line.match(/^([^=]+)=(.*)$/); if (match) { let val = match[2].trim(); if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1); env[match[1].trim()] = val; } }); const CLIENT_ID = env.APPLE_SEARCH_ADS_CLIENT_ID; const TEAM_ID = env.APPLE_SEARCH_ADS_TEAM_ID; const KEY_ID = env.APPLE_SEARCH_ADS_KEY_ID; const PRIVATE_KEY = env.APPLE_SEARCH_ADS_PRIVATE_KEY; const ORG_ID = 22309440; function generateClientSecret() { const header = { alg: "ES256", kid: KEY_ID, typ: "JWT" }; const now = Math.floor(Date.now() / 1000); const payload = { sub: CLIENT_ID, aud: "https://appleid.apple.com", iat: now, exp: now + 86400 * 180, iss: TEAM_ID }; const base64Header = Buffer.from(JSON.stringify(header)).toString("base64url"); const base64Payload = Buffer.from(JSON.stringify(payload)).toString("base64url"); const signingInput = `${base64Header}.${base64Payload}`; const signer = crypto.createSign("SHA256"); signer.update(signingInput); const signature = signer.sign({ key: PRIVATE_KEY.replace(/\\n/g, '\n'), dsaEncoding: 'ieee-p1363' }, "base64url"); return `${signingInput}.${signature}`; } async function getAccessToken(clientSecret) { return new Promise((resolve, reject) => { const postData = new URLSearchParams({ grant_type: 'client_credentials', client_id: CLIENT_ID, client_secret: clientSecret, scope: 'searchadsorg' }).toString(); const req = https.request({ hostname: 'appleid.apple.com', port: 443, path: '/auth/oauth2/token', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData), 'User-Agent': 'Mozilla/5.0' } }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(JSON.parse(data))); }); req.on('error', reject); req.write(postData); req.end(); }); } function reqApi(path, token, method = 'GET', body = null) { return new Promise((resolve) => { const headers = { 'Authorization': `Bearer ${token}`, 'X-AP-Context': `orgId=${ORG_ID}`, 'User-Agent': 'Mozilla/5.0' }; if (body) headers['Content-Type'] = 'application/json'; const req = https.request({ hostname: 'api.searchads.apple.com', port: 443, path, method, headers }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ raw: data, status: res.statusCode }); } }); }); req.on('error', (e) => resolve({ error: e.message })); if (body) req.write(JSON.stringify(body)); req.end(); }); } async function run() { const secret = generateClientSecret(); const token = (await getAccessToken(secret)).access_token; console.log("Token obtained. Testing keyword recommendation endpoints..."); const endpoints = [ { path: '/api/v5/keywords/recommendation', method: 'POST', body: { adamId: 6759843546, countryOrRegionCode: 'US' } }, { path: '/api/v5/keywords/recommendation', method: 'POST', body: { adamId: 6759843546, countryOrRegionCode: 'DE' } }, { path: '/api/v5/keywords/recommendation', method: 'POST', body: { searchTerms: ['receipt scanner', 'receipt to excel', 'expense tracker'], countryOrRegionCode: 'US' } }, { path: '/api/v5/keywords/search/recommendations', method: 'POST', body: { searchTerms: ['receipt scanner'], countryOrRegionCode: 'US' } } ]; for (const ep of endpoints) { const res = await reqApi(ep.path, token, ep.method, ep.body); console.log(`\nEndpoint ${ep.method} ${ep.path}:`); console.log(JSON.stringify(res, null, 2).slice(0, 500)); } } run();