fix: address codex review findings across new CLI tools
- supermetrics: fix query endpoint /query → /query/data/json - airops: fix base URL /v1 → /public_api/v1 - zoominfo: fix auth --dry-run leaking real JWT, add response validation - outreach: remove parseInt() on JSON:API string IDs (caused NaN) - similarweb: add encodeURIComponent on domain in all URL paths - coupler: fix dry-run auth mask from '***' to 'Bearer ***' - clay: allow name-based enrich (--first-name + --last-name + --domain) - pendo.md: fix guide state from 'published' to 'public' - close.md: fix rate limit header names to ratelimit-* Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4ff486a702
commit
9a2bb97784
11 changed files with 41 additions and 31 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const API_KEY = process.env.AIROPS_API_KEY
|
||||
const WORKSPACE_ID = process.env.AIROPS_WORKSPACE_ID
|
||||
const BASE_URL = 'https://api.airops.com/v1'
|
||||
const BASE_URL = 'https://api.airops.com/public_api/v1'
|
||||
|
||||
if (!API_KEY) {
|
||||
console.error(JSON.stringify({ error: 'AIROPS_API_KEY environment variable required' }))
|
||||
|
|
|
|||
|
|
@ -104,8 +104,9 @@ async function main() {
|
|||
if (args.linkedin) body.linkedin_url = args.linkedin
|
||||
if (args['first-name']) body.first_name = args['first-name']
|
||||
if (args['last-name']) body.last_name = args['last-name']
|
||||
if (!args.email && !args.linkedin) {
|
||||
result = { error: '--email or --linkedin required' }
|
||||
if (args['first-name'] && args['last-name'] && args.domain) body.domain = args.domain
|
||||
if (!args.email && !args.linkedin && !(args['first-name'] && args['last-name'] && args.domain)) {
|
||||
result = { error: '--email or --linkedin required (or --first-name + --last-name + --domain)' }
|
||||
break
|
||||
}
|
||||
result = await api('POST', '/people/enrich', body)
|
||||
|
|
@ -140,7 +141,7 @@ async function main() {
|
|||
'add-row': 'tables add-row --id <table_id> --data <json>',
|
||||
},
|
||||
people: {
|
||||
enrich: 'people enrich --email <email> | --linkedin <url>',
|
||||
enrich: 'people enrich --email <email> | --linkedin <url> | --first-name <n> --last-name <n> --domain <d>',
|
||||
},
|
||||
companies: {
|
||||
enrich: 'companies enrich --domain <domain>',
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ if (!API_KEY) {
|
|||
|
||||
async function api(method, path, body) {
|
||||
if (args['dry-run']) {
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': '***', 'Content-Type': 'application/json' }, body: body || undefined }
|
||||
return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined }
|
||||
}
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
method,
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ async function main() {
|
|||
data: {
|
||||
type: 'sequenceState',
|
||||
relationships: {
|
||||
prospect: { data: { type: 'prospect', id: parseInt(prospectId) } },
|
||||
sequence: { data: { type: 'sequence', id: parseInt(sequenceId) } },
|
||||
prospect: { data: { type: 'prospect', id: prospectId } },
|
||||
sequence: { data: { type: 'sequence', id: sequenceId } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ async function main() {
|
|||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
if (args.granularity) params.set('granularity', args.granularity)
|
||||
result = await api('GET', `/website/${domain}/total-traffic-and-engagement/visits?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/visits?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'pages-per-visit': {
|
||||
|
|
@ -78,7 +78,7 @@ async function main() {
|
|||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
if (args.granularity) params.set('granularity', args.granularity)
|
||||
result = await api('GET', `/website/${domain}/total-traffic-and-engagement/pages-per-visit?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/pages-per-visit?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'avg-duration': {
|
||||
|
|
@ -89,7 +89,7 @@ async function main() {
|
|||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
if (args.granularity) params.set('granularity', args.granularity)
|
||||
result = await api('GET', `/website/${domain}/total-traffic-and-engagement/average-visit-duration?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/average-visit-duration?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'bounce-rate': {
|
||||
|
|
@ -100,7 +100,7 @@ async function main() {
|
|||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
if (args.granularity) params.set('granularity', args.granularity)
|
||||
result = await api('GET', `/website/${domain}/total-traffic-and-engagement/bounce-rate?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/bounce-rate?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'sources': {
|
||||
|
|
@ -110,7 +110,7 @@ async function main() {
|
|||
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
|
||||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
result = await api('GET', `/website/${domain}/traffic-sources/overview?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/traffic-sources/overview?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
|
@ -125,7 +125,7 @@ async function main() {
|
|||
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
|
||||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
result = await api('GET', `/website/${domain}/traffic-sources/referrals?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/traffic-sources/referrals?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ async function main() {
|
|||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
result = await api('GET', `/website/${domain}/search/organic-search-keywords?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/search/organic-search-keywords?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
case 'keywords-paid': {
|
||||
|
|
@ -150,7 +150,7 @@ async function main() {
|
|||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
if (args.country) params.set('country', args.country)
|
||||
if (args.limit) params.set('limit', args.limit)
|
||||
result = await api('GET', `/website/${domain}/search/paid-search-keywords?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/search/paid-search-keywords?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
|
@ -161,14 +161,14 @@ async function main() {
|
|||
case 'competitors': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
result = await api('GET', `/website/${domain}/similar-sites/similarsites`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/similar-sites/similarsites`)
|
||||
break
|
||||
}
|
||||
|
||||
case 'category-rank': {
|
||||
const domain = args.domain
|
||||
if (!domain) { result = { error: '--domain required' }; break }
|
||||
result = await api('GET', `/website/${domain}/category-rank/category-rank`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/category-rank/category-rank`)
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ async function main() {
|
|||
if (!args.start) { result = { error: '--start required (YYYY-MM)' }; break }
|
||||
if (!args.end) { result = { error: '--end required (YYYY-MM)' }; break }
|
||||
const params = new URLSearchParams({ start_date: args.start, end_date: args.end })
|
||||
result = await api('GET', `/website/${domain}/geo/traffic-by-country?${params.toString()}`)
|
||||
result = await api('GET', `/website/${encodeURIComponent(domain)}/geo/traffic-by-country?${params.toString()}`)
|
||||
break
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ async function main() {
|
|||
if (args['max-rows']) body.max_rows = parseInt(args['max-rows'], 10)
|
||||
if (args['start-date']) body.start_date = args['start-date']
|
||||
if (args['end-date']) body.end_date = args['end-date']
|
||||
result = await api('POST', '/query', body)
|
||||
result = await api('POST', '/query/data/json', body)
|
||||
break
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,16 @@ async function authenticate() {
|
|||
body: JSON.stringify({ username, password }),
|
||||
})
|
||||
const text = await res.text()
|
||||
if (!res.ok) {
|
||||
throw new Error(`Authentication failed (${res.status}): ${text}`)
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(text)
|
||||
if (!data.jwt) throw new Error('No JWT in response')
|
||||
ACCESS_TOKEN = data.jwt
|
||||
return ACCESS_TOKEN
|
||||
} catch {
|
||||
} catch (e) {
|
||||
if (e.message === 'No JWT in response') throw e
|
||||
throw new Error(`Authentication failed: ${text}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -82,6 +87,10 @@ async function main() {
|
|||
|
||||
switch (cmd) {
|
||||
case 'auth': {
|
||||
if (args['dry-run']) {
|
||||
result = { _dry_run: true, action: 'authenticate', url: `${BASE_URL}/authenticate`, jwt: '***' }
|
||||
break
|
||||
}
|
||||
const token = await authenticate()
|
||||
result = { jwt: token }
|
||||
break
|
||||
|
|
|
|||
|
|
@ -23,19 +23,19 @@ AI content platform for crafting content that wins AI search. Build and execute
|
|||
### List Flows
|
||||
|
||||
```bash
|
||||
GET https://api.airops.com/v1/workspaces/{workspace_id}/flows
|
||||
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows
|
||||
```
|
||||
|
||||
### Get Flow Details
|
||||
|
||||
```bash
|
||||
GET https://api.airops.com/v1/workspaces/{workspace_id}/flows/{flow_id}
|
||||
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}
|
||||
```
|
||||
|
||||
### Execute a Flow
|
||||
|
||||
```bash
|
||||
POST https://api.airops.com/v1/workspaces/{workspace_id}/flows/{flow_id}/execute
|
||||
POST https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}/execute
|
||||
|
||||
{
|
||||
"inputs": {
|
||||
|
|
@ -48,25 +48,25 @@ POST https://api.airops.com/v1/workspaces/{workspace_id}/flows/{flow_id}/execute
|
|||
### List Runs for a Flow
|
||||
|
||||
```bash
|
||||
GET https://api.airops.com/v1/workspaces/{workspace_id}/flows/{flow_id}/runs
|
||||
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}/runs
|
||||
```
|
||||
|
||||
### Get Run Status
|
||||
|
||||
```bash
|
||||
GET https://api.airops.com/v1/workspaces/{workspace_id}/runs/{run_id}
|
||||
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/runs/{run_id}
|
||||
```
|
||||
|
||||
### List Workflows
|
||||
|
||||
```bash
|
||||
GET https://api.airops.com/v1/workspaces/{workspace_id}/workflows
|
||||
GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/workflows
|
||||
```
|
||||
|
||||
### Execute a Workflow
|
||||
|
||||
```bash
|
||||
POST https://api.airops.com/v1/workspaces/{workspace_id}/workflows/{workflow_id}/execute
|
||||
POST https://api.airops.com/public_api/v1/workspaces/{workspace_id}/workflows/{workflow_id}/execute
|
||||
|
||||
{
|
||||
"inputs": {
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ POST https://api.close.com/api/v1/task/
|
|||
|
||||
- Rate limits based on organization plan
|
||||
- Standard: ~100 requests/minute
|
||||
- Responses include `X-Rate-Limit-Limit` and `X-Rate-Limit-Remaining` headers
|
||||
- Responses include `ratelimit-limit` and `ratelimit-remaining` headers
|
||||
- 429 responses include `Retry-After` header
|
||||
|
||||
## Relevant Skills
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ GET https://app.pendo.io/api/v1/page/{pageId}
|
|||
### List Guides
|
||||
|
||||
```bash
|
||||
GET https://app.pendo.io/api/v1/guide?state=published
|
||||
GET https://app.pendo.io/api/v1/guide?state=public
|
||||
```
|
||||
|
||||
### Get Guide Details
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Marketing data pipeline that connects 200+ marketing platforms. Pulls data from
|
|||
### Query a Data Source
|
||||
|
||||
```bash
|
||||
POST https://api.supermetrics.com/enterprise/v2/query
|
||||
POST https://api.supermetrics.com/enterprise/v2/query/data/json
|
||||
|
||||
{
|
||||
"ds_id": "GA4",
|
||||
|
|
@ -39,7 +39,7 @@ POST https://api.supermetrics.com/enterprise/v2/query
|
|||
### Query with Filters
|
||||
|
||||
```bash
|
||||
POST https://api.supermetrics.com/enterprise/v2/query
|
||||
POST https://api.supermetrics.com/enterprise/v2/query/data/json
|
||||
|
||||
{
|
||||
"ds_id": "AW",
|
||||
|
|
|
|||
Loading…
Reference in a new issue