fix: correct API issues found by second codex review across 14 CLIs
Fixes from thorough codex review (o3 high reasoning, 5 parallel batches): Validation fixes: - customer-io: add ID validation for customer/campaign commands, event name check - dub: fix links get to use /links/info endpoint, add --id validation - google-search-console: fix countries to use ['country'] only, add --url validation - mention-me: add --customer-id validation on referral/share/reward commands - tolt: add --id validation for affiliates get/update Auth & API fixes: - apollo: move API key from header to JSON body, fix search endpoint path - rewardful: change from Bearer to Basic auth - hotjar: split OAuth URL (unversioned) from resource URL (v2) - amplitude: wrap retention e param in JSON array - snov: change list prospects from GET to POST with JSON body - optimizely: change archive from DELETE to PATCH status=archived - google-ads: fix budget body field from camelCase to snake_case - resend: change webhook field from endpoint to url, add validation - linkedin-ads: add required campaignGroup URN, fix numeric amount types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aad399682c
commit
ebdf1dd2f1
14 changed files with 62 additions and 29 deletions
|
|
@ -149,7 +149,7 @@ async function main() {
|
|||
params.set('start', args.start)
|
||||
params.set('end', args.end)
|
||||
if (args.event) {
|
||||
params.set('e', JSON.stringify({ event_type: args.event }))
|
||||
params.set('e', JSON.stringify([{ event_type: args.event }]))
|
||||
}
|
||||
result = await queryApi('GET', '/retention', params)
|
||||
break
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@ if (!API_KEY) {
|
|||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const authBody = body ? { ...body, api_key: API_KEY } : { api_key: API_KEY }
|
||||
if (args['dry-run']) {
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'x-api-key': '***', 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined }
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Content-Type': 'application/json' }, body: { ...authBody, api_key: '***' } }
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'x-api-key': API_KEY,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
body: JSON.stringify(authBody),
|
||||
})
|
||||
const text = await res.text()
|
||||
try {
|
||||
|
|
@ -67,7 +66,7 @@ async function main() {
|
|||
if (args.seniorities) body.person_seniorities = args.seniorities.split(',')
|
||||
if (args['employee-ranges']) body.organization_num_employees_ranges = args['employee-ranges'].split(',').map(r => r.trim())
|
||||
if (args.keywords) body.q_keywords = args.keywords
|
||||
result = await api('POST', '/mixed_people/api_search', body)
|
||||
result = await api('POST', '/mixed_people/search', body)
|
||||
break
|
||||
}
|
||||
case 'enrich': {
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ async function main() {
|
|||
switch (sub) {
|
||||
case 'identify': {
|
||||
const customerId = rest[0] || args.id
|
||||
if (!customerId) { result = { error: 'Customer ID required (positional arg or --id)' }; break }
|
||||
const body = {}
|
||||
if (args.email) body.email = args.email
|
||||
if (args['first-name']) body.first_name = args['first-name']
|
||||
|
|
@ -106,16 +107,20 @@ async function main() {
|
|||
}
|
||||
case 'get': {
|
||||
const customerId = rest[0] || args.id
|
||||
if (!customerId) { result = { error: 'Customer ID required (positional arg or --id)' }; break }
|
||||
result = await appApi('GET', `/customers/${customerId}/attributes`)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
const customerId = rest[0] || args.id
|
||||
if (!customerId) { result = { error: 'Customer ID required (positional arg or --id)' }; break }
|
||||
result = await trackApi('DELETE', `/customers/${customerId}`)
|
||||
break
|
||||
}
|
||||
case 'track-event': {
|
||||
const customerId = rest[0] || args.id
|
||||
if (!customerId) { result = { error: 'Customer ID required (positional arg or --id)' }; break }
|
||||
if (!args.name) { result = { error: '--name required (event name)' }; break }
|
||||
const body = { name: args.name }
|
||||
if (args.data) body.data = JSON.parse(args.data)
|
||||
result = await trackApi('POST', `/customers/${customerId}/events`, body)
|
||||
|
|
@ -131,18 +136,26 @@ async function main() {
|
|||
case 'list':
|
||||
result = await appApi('GET', '/campaigns')
|
||||
break
|
||||
case 'get':
|
||||
result = await appApi('GET', `/campaigns/${rest[0]}`)
|
||||
case 'get': {
|
||||
const campaignId = rest[0] || args.id
|
||||
if (!campaignId) { result = { error: 'Campaign ID required (positional arg or --id)' }; break }
|
||||
result = await appApi('GET', `/campaigns/${campaignId}`)
|
||||
break
|
||||
case 'metrics':
|
||||
result = await appApi('GET', `/campaigns/${rest[0]}/metrics`)
|
||||
}
|
||||
case 'metrics': {
|
||||
const campaignId = rest[0] || args.id
|
||||
if (!campaignId) { result = { error: 'Campaign ID required (positional arg or --id)' }; break }
|
||||
result = await appApi('GET', `/campaigns/${campaignId}/metrics`)
|
||||
break
|
||||
}
|
||||
case 'trigger': {
|
||||
const campaignId = rest[0] || args.id
|
||||
if (!campaignId) { result = { error: 'Campaign ID required (positional arg or --id)' }; break }
|
||||
const body = {}
|
||||
if (args.emails) body.emails = args.emails.split(',')
|
||||
if (args.ids) body.ids = args.ids.split(',')
|
||||
if (args.data) body.data = JSON.parse(args.data)
|
||||
result = await appApi('POST', `/campaigns/${rest[0]}/triggers`, body)
|
||||
result = await appApi('POST', `/campaigns/${campaignId}/triggers`, body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -77,10 +77,13 @@ async function main() {
|
|||
const params = new URLSearchParams()
|
||||
if (args.domain) params.set('domain', args.domain)
|
||||
if (args.key) params.set('key', args.key)
|
||||
result = await api('GET', `/links?${params}`)
|
||||
if (args['link-id']) params.set('linkId', args['link-id'])
|
||||
if (args['external-id']) params.set('externalId', args['external-id'])
|
||||
result = await api('GET', `/links/info?${params}`)
|
||||
break
|
||||
}
|
||||
case 'update': {
|
||||
if (!args.id) { result = { error: '--id required (link ID)' }; break }
|
||||
const body = {}
|
||||
if (args.url) body.url = args.url
|
||||
if (args.tags) body.tags = args.tags.split(',')
|
||||
|
|
@ -88,6 +91,7 @@ async function main() {
|
|||
break
|
||||
}
|
||||
case 'delete':
|
||||
if (!args.id) { result = { error: '--id required (link ID)' }; break }
|
||||
result = await api('DELETE', `/links/${args.id}`)
|
||||
break
|
||||
case 'bulk-create': {
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ async function main() {
|
|||
operations: [{
|
||||
update: {
|
||||
resourceName: `customers/${CUSTOMER_ID}/campaignBudgets/${args.id}`,
|
||||
amountMicros,
|
||||
amount_micros: amountMicros,
|
||||
},
|
||||
updateMask: 'amount_micros',
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ async function main() {
|
|||
result = await api('POST', `/webmasters/v3/sites/${encodedSiteUrl}/searchAnalytics/query`, body)
|
||||
break
|
||||
case 'countries':
|
||||
body.dimensions = ['country', 'query']
|
||||
body.dimensions = ['country']
|
||||
result = await api('POST', `/webmasters/v3/sites/${encodedSiteUrl}/searchAnalytics/query`, body)
|
||||
break
|
||||
default:
|
||||
|
|
@ -106,6 +106,7 @@ async function main() {
|
|||
}
|
||||
switch (sub) {
|
||||
case 'url':
|
||||
if (!args.url) { result = { error: '--url required (URL to inspect)' }; break }
|
||||
result = await api('POST', '/v1/urlInspection/index:inspect', {
|
||||
inspectionUrl: args.url,
|
||||
siteUrl: siteUrl,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
const CLIENT_ID = process.env.HOTJAR_CLIENT_ID
|
||||
const CLIENT_SECRET = process.env.HOTJAR_CLIENT_SECRET
|
||||
const BASE_URL = 'https://api.hotjar.io/v1'
|
||||
const OAUTH_URL = 'https://api.hotjar.io'
|
||||
const BASE_URL = 'https://api.hotjar.io/v2'
|
||||
|
||||
if (!CLIENT_ID || !CLIENT_SECRET) {
|
||||
console.error(JSON.stringify({ error: 'HOTJAR_CLIENT_ID and HOTJAR_CLIENT_SECRET environment variables required' }))
|
||||
|
|
@ -13,7 +14,7 @@ let cachedToken = null
|
|||
|
||||
async function getToken() {
|
||||
if (cachedToken) return cachedToken
|
||||
const res = await fetch(`${BASE_URL}/oauth/token`, {
|
||||
const res = await fetch(`${OAUTH_URL}/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `grant_type=client_credentials&client_id=${encodeURIComponent(CLIENT_ID)}&client_secret=${encodeURIComponent(CLIENT_SECRET)}`,
|
||||
|
|
|
|||
|
|
@ -76,17 +76,19 @@ async function main() {
|
|||
}
|
||||
case 'create': {
|
||||
if (!args['account-id'] || !args.name) { result = { error: '--account-id and --name required' }; break }
|
||||
if (!args['campaign-group-id']) { result = { error: '--campaign-group-id required' }; break }
|
||||
const body = {
|
||||
account: `urn:li:sponsoredAccount:${args['account-id']}`,
|
||||
campaignGroup: `urn:li:sponsoredCampaignGroup:${args['campaign-group-id']}`,
|
||||
name: args.name,
|
||||
type: args.type || 'SPONSORED_UPDATES',
|
||||
costType: args['cost-type'] || 'CPC',
|
||||
unitCost: {
|
||||
amount: args['unit-cost'] || '5.00',
|
||||
amount: parseFloat(args['unit-cost'] || '5.00'),
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
dailyBudget: {
|
||||
amount: args['daily-budget'] || '100.00',
|
||||
amount: parseFloat(args['daily-budget'] || '100.00'),
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
status: 'PAUSED',
|
||||
|
|
|
|||
|
|
@ -76,10 +76,14 @@ async function main() {
|
|||
|
||||
case 'referrals':
|
||||
switch (sub) {
|
||||
case 'get':
|
||||
result = await api('GET', `/referral/${rest[0]}`)
|
||||
case 'get': {
|
||||
const refId = rest[0] || args.id
|
||||
if (!refId) { result = { error: 'Referral ID required (positional arg or --id)' }; break }
|
||||
result = await api('GET', `/referral/${refId}`)
|
||||
break
|
||||
}
|
||||
case 'list': {
|
||||
if (!args['customer-id']) { result = { error: '--customer-id required' }; break }
|
||||
result = await api('GET', `/referrer/${args['customer-id']}/referrals`)
|
||||
break
|
||||
}
|
||||
|
|
@ -91,6 +95,7 @@ async function main() {
|
|||
case 'share-links':
|
||||
switch (sub) {
|
||||
case 'get':
|
||||
if (!args['customer-id']) { result = { error: '--customer-id required' }; break }
|
||||
result = await api('GET', `/referrer/${args['customer-id']}/share-links`)
|
||||
break
|
||||
default:
|
||||
|
|
@ -101,9 +106,11 @@ async function main() {
|
|||
case 'rewards':
|
||||
switch (sub) {
|
||||
case 'get':
|
||||
if (!args['customer-id']) { result = { error: '--customer-id required' }; break }
|
||||
result = await api('GET', `/referrer/${args['customer-id']}/rewards`)
|
||||
break
|
||||
case 'redeem': {
|
||||
if (!args['customer-id']) { result = { error: '--customer-id required' }; break }
|
||||
const body = {}
|
||||
if (args['reward-id']) body.reward_id = args['reward-id']
|
||||
if (args['order-number']) body.order_number = args['order-number']
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ async function main() {
|
|||
case 'archive': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
result = await api('DELETE', `/experiments/${id}`)
|
||||
result = await api('PATCH', `/experiments/${id}`, { status: 'archived' })
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -202,8 +202,9 @@ async function main() {
|
|||
result = await api('GET', `/webhooks/${rest[0]}`)
|
||||
break
|
||||
case 'create': {
|
||||
if (!args.url) { result = { error: '--url required (webhook URL)' }; break }
|
||||
const events = args.events?.split(',') || ['email.sent', 'email.delivered', 'email.bounced']
|
||||
result = await api('POST', '/webhooks', { endpoint: args.endpoint, events })
|
||||
result = await api('POST', '/webhooks', { url: args.url, events })
|
||||
break
|
||||
}
|
||||
case 'delete':
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ if (!API_KEY) {
|
|||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const auth = 'Basic ' + Buffer.from(`${API_KEY}:`).toString('base64')
|
||||
if (args['dry-run']) {
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { Authorization: '***', 'Content-Type': 'application/json' }, body: body || undefined }
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
'Authorization': auth,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
|
|
|
|||
|
|
@ -148,10 +148,10 @@ async function main() {
|
|||
case 'prospects': {
|
||||
const id = args.id
|
||||
if (!id) { result = { error: '--id required' }; break }
|
||||
const params = new URLSearchParams({ listId: id })
|
||||
if (args.page) params.set('page', args.page)
|
||||
if (args['per-page']) params.set('perPage', args['per-page'])
|
||||
result = await api('GET', `/prospect-list?${params.toString()}`)
|
||||
const body = { listId: id }
|
||||
if (args.page) body.page = Number(args.page)
|
||||
if (args['per-page']) body.perPage = Number(args['per-page'])
|
||||
result = await api('POST', '/prospect-list', body)
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -60,9 +60,12 @@ async function main() {
|
|||
case 'list':
|
||||
result = await api('GET', '/affiliates')
|
||||
break
|
||||
case 'get':
|
||||
result = await api('GET', `/affiliates/${rest[0]}`)
|
||||
case 'get': {
|
||||
const id = rest[0] || args.id
|
||||
if (!id) { result = { error: 'Affiliate ID required (positional arg or --id)' }; break }
|
||||
result = await api('GET', `/affiliates/${id}`)
|
||||
break
|
||||
}
|
||||
case 'create': {
|
||||
const body = {}
|
||||
if (args.email) body.email = args.email
|
||||
|
|
@ -71,6 +74,7 @@ async function main() {
|
|||
break
|
||||
}
|
||||
case 'update': {
|
||||
if (!args.id) { result = { error: '--id required (affiliate ID)' }; break }
|
||||
const body = {}
|
||||
if (args['commission-rate']) body.commission_rate = Number(args['commission-rate'])
|
||||
if (args['payout-method']) body.payout_method = args['payout-method']
|
||||
|
|
|
|||
Loading…
Reference in a new issue