diff --git a/AGENTS.md b/AGENTS.md index 9344fda..e53c96b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,7 +167,7 @@ This repository includes a tools registry for agent-compatible marketing tools. - **Tool discovery**: Read `tools/REGISTRY.md` to see available tools and their capabilities - **Integration details**: See `tools/integrations/{tool}.md` for API endpoints, auth, and common operations -- **MCP-enabled tools**: ga4, stripe, mailchimp, google-ads, resend, zapier +- **MCP-enabled tools**: ga4, stripe, mailchimp, google-ads, resend, zapier, zoominfo, clay, supermetrics, coupler, outreach, crossbeam ### Registry Structure diff --git a/tools/REGISTRY.md b/tools/REGISTRY.md index 76eb2f2..52bb118 100644 --- a/tools/REGISTRY.md +++ b/tools/REGISTRY.md @@ -28,8 +28,13 @@ Quick reference for AI agents to discover tool capabilities and integration meth | keywords-everywhere | SEO | ✓ | - | [✓](clis/keywords-everywhere.js) | - | [keywords-everywhere.md](integrations/keywords-everywhere.md) | | clearbit | Data Enrichment | ✓ | - | [✓](clis/clearbit.js) | ✓ | [clearbit.md](integrations/clearbit.md) | | apollo | Data Enrichment | ✓ | - | [✓](clis/apollo.js) | - | [apollo.md](integrations/apollo.md) | +| zoominfo | Data Enrichment | ✓ | ✓ | [✓](clis/zoominfo.js) | - | [zoominfo.md](integrations/zoominfo.md) | +| clay | Data Enrichment | ✓ | ✓ | [✓](clis/clay.js) | - | [clay.md](integrations/clay.md) | +| supermetrics | Data Aggregation | ✓ | ✓ | [✓](clis/supermetrics.js) | - | [supermetrics.md](integrations/supermetrics.md) | +| coupler | Data Aggregation | ✓ | ✓ | [✓](clis/coupler.js) | - | [coupler.md](integrations/coupler.md) | | hubspot | CRM | ✓ | - | ✓ | ✓ | [hubspot.md](integrations/hubspot.md) | | salesforce | CRM | ✓ | - | ✓ | ✓ | [salesforce.md](integrations/salesforce.md) | +| close | CRM | ✓ | - | [✓](clis/close.js) | - | [close.md](integrations/close.md) | | stripe | Payments | ✓ | ✓ | ✓ | ✓ | [stripe.md](integrations/stripe.md) | | paddle | Payments | ✓ | - | [✓](clis/paddle.js) | ✓ | [paddle.md](integrations/paddle.md) | | rewardful | Referral | ✓ | - | [✓](clis/rewardful.js) | - | [rewardful.md](integrations/rewardful.md) | @@ -62,6 +67,11 @@ Quick reference for AI agents to discover tool capabilities and integration meth | savvycal | Scheduling | ✓ | - | [✓](clis/savvycal.js) | - | [savvycal.md](integrations/savvycal.md) | | typeform | Forms | ✓ | - | [✓](clis/typeform.js) | ✓ | [typeform.md](integrations/typeform.md) | | intercom | Messaging | ✓ | - | [✓](clis/intercom.js) | ✓ | [intercom.md](integrations/intercom.md) | +| outreach | Sales Engagement | ✓ | ✓ | [✓](clis/outreach.js) | - | [outreach.md](integrations/outreach.md) | +| crossbeam | Partner Ecosystem | ✓ | ✓ | [✓](clis/crossbeam.js) | - | [crossbeam.md](integrations/crossbeam.md) | +| pendo | Product Analytics | ✓ | - | [✓](clis/pendo.js) | - | [pendo.md](integrations/pendo.md) | +| similarweb | Competitive Intelligence | ✓ | - | [✓](clis/similarweb.js) | - | [similarweb.md](integrations/similarweb.md) | +| airops | AI Content | ✓ | - | [✓](clis/airops.js) | - | [airops.md](integrations/airops.md) | | buffer | Social | ✓ | - | [✓](clis/buffer.js) | - | [buffer.md](integrations/buffer.md) | | wistia | Video | ✓ | - | [✓](clis/wistia.js) | - | [wistia.md](integrations/wistia.md) | | trustpilot | Reviews | ✓ | - | [✓](clis/trustpilot.js) | - | [trustpilot.md](integrations/trustpilot.md) | @@ -115,8 +125,9 @@ Customer relationship management and sales tools. |------|----------|:-------------:| | **hubspot** | SMB, marketing + sales alignment | ✓ | | **salesforce** | Enterprise, complex sales processes | ✓ | +| **close** | SMB, high-velocity sales | [✓](clis/close.js) | -**Agent recommendation**: HubSpot for startups/SMBs, Salesforce for enterprise. +**Agent recommendation**: HubSpot for startups/SMBs. Close for high-velocity inside sales. Salesforce for enterprise. ### Payments @@ -125,7 +136,6 @@ Payment processing and subscription management. | Tool | Best For | MCP Available | |------|----------|:-------------:| | **stripe** | SaaS subscriptions, developer-friendly | ✓ | - | **paddle** | SaaS billing with tax handling | - | **Agent recommendation**: Stripe is the default for SaaS. Paddle for built-in tax compliance. @@ -256,8 +266,10 @@ Company and person data enrichment for sales and marketing. |------|----------|-------| | **clearbit** | Company/person enrichment | Now HubSpot Breeze | | **apollo** | B2B prospecting, email finding | Large database | +| **zoominfo** | B2B contacts, intent data | Enterprise-grade | +| **clay** | Waterfall enrichment, outbound | 75+ data providers | -**Agent recommendation**: Clearbit for enrichment. Apollo for prospecting and outbound. +**Agent recommendation**: Clearbit for enrichment. Apollo for prospecting and outbound. ZoomInfo for enterprise B2B data with intent signals. Clay for waterfall enrichment across multiple providers. ### Reviews @@ -291,6 +303,56 @@ Webinar and virtual event platforms. **Agent recommendation**: Demio for marketing-focused webinars. Livestorm for full event engagement. +### Sales Engagement + +Sales engagement and outreach automation platforms. + +| Tool | Best For | Notes | +|------|----------|-------| +| **outreach** | Enterprise sales engagement | Sequences, tasks, analytics | + +**Agent recommendation**: Outreach for enterprise sales teams managing multi-touch sequences at scale. + +### Product Analytics + +Product analytics, feature adoption tracking, and in-app guidance. + +| Tool | Best For | Notes | +|------|----------|-------| +| **pendo** | Feature adoption, in-app guides | Product-led growth | + +**Agent recommendation**: Pendo for tracking feature adoption and delivering targeted in-app guidance. + +### Competitive Intelligence + +Traffic analytics, competitor benchmarking, and market research. + +| Tool | Best For | Notes | +|------|----------|-------| +| **similarweb** | Website traffic, competitor analysis | Traffic sources, keywords | + +**Agent recommendation**: Similarweb for competitor traffic analysis and market benchmarking. + +### AI Content + +AI-powered content generation and optimization platforms. + +| Tool | Best For | Notes | +|------|----------|-------| +| **airops** | AI content workflows, SEO content | Flow-based automation | + +**Agent recommendation**: AirOps for building AI content workflows that generate SEO-optimized content at scale. + +### Partner Ecosystem + +Partner data sharing, co-sell, and ecosystem management. + +| Tool | Best For | Notes | +|------|----------|-------| +| **crossbeam** | Account overlaps, co-sell | Now part of Reveal | + +**Agent recommendation**: Crossbeam for identifying partner account overlaps and co-sell opportunities. + ### Email Outreach Cold email outreach and email finding tools for link building and sales prospecting. @@ -304,6 +366,17 @@ Cold email outreach and email finding tools for link building and sales prospect **Agent recommendation**: Hunter for finding emails. Lemlist or Instantly for sending cold email campaigns. Snov for combined finding + outreach. +### Data Aggregation + +Marketing data pipeline tools that connect multiple platforms for unified reporting. + +| Tool | Best For | Notes | +|------|----------|-------| +| **supermetrics** | Cross-platform data pulling | 200+ connectors | +| **coupler** | Automated data flows to sheets/BI | Scheduled pipelines | + +**Agent recommendation**: Supermetrics for pulling data from multiple marketing platforms into unified reports. Coupler.io for automated data flows to spreadsheets and BI tools. + ### Commerce & CMS E-commerce platforms and content management systems. @@ -340,6 +413,12 @@ These tools have Model Context Protocol servers available, enabling direct agent - **google-ads** - Ad campaign management - **resend** - Transactional email sending - **zapier** - Workflow automation +- **zoominfo** - B2B contacts and intent data +- **clay** - Data enrichment and outbound automation +- **supermetrics** - Cross-platform marketing data +- **coupler** - Marketing data pipelines +- **outreach** - Sales engagement sequences +- **crossbeam** - Partner ecosystem data To use MCP tools, ensure the appropriate MCP server is configured in your environment. diff --git a/tools/clis/airops.js b/tools/clis/airops.js new file mode 100755 index 0000000..9a99ad1 --- /dev/null +++ b/tools/clis/airops.js @@ -0,0 +1,163 @@ +#!/usr/bin/env node + +const API_KEY = process.env.AIROPS_API_KEY +const WORKSPACE_ID = process.env.AIROPS_WORKSPACE_ID +const BASE_URL = 'https://api.airops.com/public_api/v1' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'AIROPS_API_KEY environment variable required' })) + process.exit(1) +} + +if (!WORKSPACE_ID) { + console.error(JSON.stringify({ error: 'AIROPS_WORKSPACE_ID environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const url = `${BASE_URL}${path}` + if (args['dry-run']) { + return { _dry_run: true, method, url, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'flows': + switch (sub) { + case 'list': { + result = await api('GET', `/workspaces/${WORKSPACE_ID}/flows`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/workspaces/${WORKSPACE_ID}/flows/${id}`) + break + } + case 'execute': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + const inputs = args.inputs + let parsedInputs = {} + if (inputs) { + try { + parsedInputs = JSON.parse(inputs) + } catch { + result = { error: '--inputs must be valid JSON' } + break + } + } + result = await api('POST', `/workspaces/${WORKSPACE_ID}/flows/${id}/execute`, { inputs: parsedInputs }) + break + } + case 'runs': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/workspaces/${WORKSPACE_ID}/flows/${id}/runs`) + break + } + case 'run-status': { + const runId = args['run-id'] + if (!runId) { result = { error: '--run-id required' }; break } + result = await api('GET', `/workspaces/${WORKSPACE_ID}/runs/${runId}`) + break + } + default: + result = { error: 'Unknown flows subcommand. Use: list, get, execute, runs, run-status' } + } + break + + case 'workflows': + switch (sub) { + case 'list': { + result = await api('GET', `/workspaces/${WORKSPACE_ID}/workflows`) + break + } + case 'execute': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + const inputs = args.inputs + let parsedInputs = {} + if (inputs) { + try { + parsedInputs = JSON.parse(inputs) + } catch { + result = { error: '--inputs must be valid JSON' } + break + } + } + result = await api('POST', `/workspaces/${WORKSPACE_ID}/workflows/${id}/execute`, { inputs: parsedInputs }) + break + } + default: + result = { error: 'Unknown workflows subcommand. Use: list, execute' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + flows: { + list: 'flows list', + get: 'flows get --id ', + execute: 'flows execute --id --inputs ', + runs: 'flows runs --id ', + 'run-status': 'flows run-status --run-id ', + }, + workflows: { + list: 'workflows list', + execute: 'workflows execute --id --inputs ', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/clay.js b/tools/clis/clay.js new file mode 100755 index 0000000..5cfb3c9 --- /dev/null +++ b/tools/clis/clay.js @@ -0,0 +1,159 @@ +#!/usr/bin/env node + +const API_KEY = process.env.CLAY_API_KEY +const BASE_URL = 'https://api.clay.com/v3' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'CLAY_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + if (args['dry-run']) { + return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined } + } + const options = { + method, + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json', + }, + } + if (body) options.body = JSON.stringify(body) + const res = await fetch(`${BASE_URL}${path}`, options) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + const page = args.page ? Number(args.page) : 1 + const perPage = args['per-page'] ? Number(args['per-page']) : 25 + + switch (cmd) { + case 'tables': + switch (sub) { + case 'list': { + result = await api('GET', `/tables?page=${page}&per_page=${perPage}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/tables/${id}`) + break + } + case 'rows': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/tables/${id}/rows?page=${page}&per_page=${perPage}`) + break + } + case 'add-row': { + const id = args.id + const data = args.data + if (!id) { result = { error: '--id required' }; break } + if (!data) { result = { error: '--data required (JSON string)' }; break } + let parsed + try { + parsed = JSON.parse(data) + } catch { + result = { error: 'Invalid JSON in --data' } + break + } + result = await api('POST', `/tables/${id}/rows`, parsed) + break + } + default: + result = { error: 'Unknown tables subcommand. Use: list, get, rows, add-row' } + } + break + + case 'people': + switch (sub) { + case 'enrich': { + const body = {} + if (args.email) body.email = args.email + 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['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) + break + } + default: + result = { error: 'Unknown people subcommand. Use: enrich' } + } + break + + case 'companies': + switch (sub) { + case 'enrich': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + result = await api('POST', '/companies/enrich', { domain }) + break + } + default: + result = { error: 'Unknown companies subcommand. Use: enrich' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + tables: { + list: 'tables list [--page ] [--per-page ]', + get: 'tables get --id ', + rows: 'tables rows --id [--page ] [--per-page ]', + 'add-row': 'tables add-row --id --data ', + }, + people: { + enrich: 'people enrich --email | --linkedin | --first-name --last-name --domain ', + }, + companies: { + enrich: 'companies enrich --domain ', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/close.js b/tools/clis/close.js new file mode 100755 index 0000000..5a49f4c --- /dev/null +++ b/tools/clis/close.js @@ -0,0 +1,232 @@ +#!/usr/bin/env node + +const API_KEY = process.env.CLOSE_API_KEY +const BASE_URL = 'https://api.close.com/api/v1' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'CLOSE_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const url = `${BASE_URL}${path}` + if (args['dry-run']) { + return { _dry_run: true, method, url, headers: { 'Content-Type': 'application/json', 'Authorization': 'Basic ***' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Basic ${btoa(API_KEY + ':')}`, + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'leads': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args.query) params.set('query', args.query) + if (args.page) params.set('_skip', (parseInt(args.page) - 1) * 100) + const qs = params.toString() + result = await api('GET', `/lead/${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/lead/${id}/`) + break + } + case 'create': { + const name = args.name + if (!name) { result = { error: '--name required' }; break } + const body = { name } + if (args.url) body.url = args.url + if (args.description) body.description = args.description + result = await api('POST', '/lead/', body) + break + } + default: + result = { error: 'Unknown leads subcommand. Use: list, get, create' } + } + break + + case 'contacts': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args['lead-id']) params.set('lead_id', args['lead-id']) + const qs = params.toString() + result = await api('GET', `/contact/${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/contact/${id}/`) + break + } + case 'create': { + const leadId = args['lead-id'] + const name = args.name + if (!leadId) { result = { error: '--lead-id required' }; break } + if (!name) { result = { error: '--name required' }; break } + const body = { lead_id: leadId, name } + if (args.email) { + body.emails = [{ email: args.email, type: 'office' }] + } + if (args.phone) { + body.phones = [{ phone: args.phone, type: 'office' }] + } + result = await api('POST', '/contact/', body) + break + } + default: + result = { error: 'Unknown contacts subcommand. Use: list, get, create' } + } + break + + case 'opportunities': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args.status) params.set('status', args.status) + const qs = params.toString() + result = await api('GET', `/opportunity/${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/opportunity/${id}/`) + break + } + case 'create': { + const leadId = args['lead-id'] + const value = args.value + if (!leadId) { result = { error: '--lead-id required' }; break } + if (!value) { result = { error: '--value required (in cents)' }; break } + const body = { lead_id: leadId, value: parseInt(value) } + if (args.status) body.status_type = args.status + result = await api('POST', '/opportunity/', body) + break + } + default: + result = { error: 'Unknown opportunities subcommand. Use: list, get, create' } + } + break + + case 'activities': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args['lead-id']) params.set('lead_id', args['lead-id']) + if (args.type) params.set('_type__type', args.type) + const qs = params.toString() + result = await api('GET', `/activity/${qs ? '?' + qs : ''}`) + break + } + default: + result = { error: 'Unknown activities subcommand. Use: list' } + } + break + + case 'tasks': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args['assigned-to']) params.set('assigned_to', args['assigned-to']) + if (args['is-complete']) params.set('is_complete', args['is-complete']) + const qs = params.toString() + result = await api('GET', `/task/${qs ? '?' + qs : ''}`) + break + } + case 'create': { + const leadId = args['lead-id'] + const text = args.text + if (!leadId) { result = { error: '--lead-id required' }; break } + if (!text) { result = { error: '--text required' }; break } + const body = { lead_id: leadId, text, _type: 'lead' } + if (args['assigned-to']) body.assigned_to = args['assigned-to'] + if (args.date) body.date = args.date + result = await api('POST', '/task/', body) + break + } + default: + result = { error: 'Unknown tasks subcommand. Use: list, create' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + leads: { + list: 'leads list [--query ] [--page ]', + get: 'leads get --id ', + create: 'leads create --name [--url ] [--description ]', + }, + contacts: { + list: 'contacts list [--lead-id ]', + get: 'contacts get --id ', + create: 'contacts create --lead-id --name [--email ] [--phone ]', + }, + opportunities: { + list: 'opportunities list [--status ]', + get: 'opportunities get --id ', + create: 'opportunities create --lead-id --value [--status ]', + }, + activities: { + list: 'activities list [--lead-id ] [--type ]', + }, + tasks: { + list: 'tasks list [--assigned-to ] [--is-complete ]', + create: 'tasks create --lead-id --text [--assigned-to ] [--date ]', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/coupler.js b/tools/clis/coupler.js new file mode 100755 index 0000000..0772e8e --- /dev/null +++ b/tools/clis/coupler.js @@ -0,0 +1,173 @@ +#!/usr/bin/env node + +const API_KEY = process.env.COUPLER_API_KEY +const BASE_URL = 'https://api.coupler.io/v1' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'COUPLER_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + if (args['dry-run']) { + 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, + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'importers': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args.limit) params.set('limit', args.limit) + if (args.offset) params.set('offset', args.offset) + const qs = params.toString() + result = await api('GET', `/importers${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/importers/${id}`) + break + } + case 'run': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('POST', `/importers/${id}/run`) + break + } + case 'create': { + const source = args.source + const destination = args.destination + const name = args.name + if (!source) { result = { error: '--source required' }; break } + if (!destination) { result = { error: '--destination required' }; break } + if (!name) { result = { error: '--name required' }; break } + const body = { source_type: source, destination_type: destination, name } + if (args.schedule) body.schedule = args.schedule + result = await api('POST', '/importers', body) + break + } + case 'delete': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('DELETE', `/importers/${id}`) + break + } + default: + result = { error: 'Unknown importers subcommand. Use: list, get, run, create, delete' } + } + break + + case 'runs': + switch (sub) { + case 'list': { + const importerId = args['importer-id'] + if (!importerId) { result = { error: '--importer-id required' }; break } + const params = new URLSearchParams() + if (args.limit) params.set('limit', args.limit) + if (args.offset) params.set('offset', args.offset) + const qs = params.toString() + result = await api('GET', `/importers/${importerId}/runs${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/runs/${id}`) + break + } + default: + result = { error: 'Unknown runs subcommand. Use: list, get' } + } + break + + case 'sources': + switch (sub) { + case 'list': + result = await api('GET', '/sources') + break + default: + result = { error: 'Unknown sources subcommand. Use: list' } + } + break + + case 'destinations': + switch (sub) { + case 'list': + result = await api('GET', '/destinations') + break + default: + result = { error: 'Unknown destinations subcommand. Use: list' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + importers: { + list: 'importers list [--limit ] [--offset ]', + get: 'importers get --id ', + run: 'importers run --id ', + create: 'importers create --source --destination --name [--schedule ]', + delete: 'importers delete --id ', + }, + runs: { + list: 'runs list --importer-id [--limit ] [--offset ]', + get: 'runs get --id ', + }, + sources: 'sources list', + destinations: 'destinations list', + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/crossbeam.js b/tools/clis/crossbeam.js new file mode 100755 index 0000000..23fc1f2 --- /dev/null +++ b/tools/clis/crossbeam.js @@ -0,0 +1,193 @@ +#!/usr/bin/env node + +const API_KEY = process.env.CROSSBEAM_API_KEY +const BASE_URL = 'https://api.crossbeam.com/v1' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'CROSSBEAM_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const url = `${BASE_URL}${path}` + if (args['dry-run']) { + return { _dry_run: true, method, url, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/json' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'partners': + switch (sub) { + case 'list': { + result = await api('GET', '/partners') + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/partners/${id}`) + break + } + default: + result = { error: 'Unknown partners subcommand. Use: list, get' } + } + break + + case 'populations': + switch (sub) { + case 'list': { + result = await api('GET', '/populations') + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/populations/${id}`) + break + } + default: + result = { error: 'Unknown populations subcommand. Use: list, get' } + } + break + + case 'overlaps': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args['partner-id']) params.set('partner_id', args['partner-id']) + if (args['population-id']) params.set('population_id', args['population-id']) + const qs = params.toString() + result = await api('GET', `/overlaps${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/overlaps/${id}`) + break + } + default: + result = { error: 'Unknown overlaps subcommand. Use: list, get' } + } + break + + case 'reports': + switch (sub) { + case 'list': { + result = await api('GET', '/reports') + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/reports/${id}`) + break + } + default: + result = { error: 'Unknown reports subcommand. Use: list, get' } + } + break + + case 'threads': + switch (sub) { + case 'list': { + result = await api('GET', '/threads') + break + } + default: + result = { error: 'Unknown threads subcommand. Use: list' } + } + break + + case 'accounts': + switch (sub) { + case 'search': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + const params = new URLSearchParams({ domain }) + result = await api('GET', `/accounts/search?${params.toString()}`) + break + } + default: + result = { error: 'Unknown accounts subcommand. Use: search' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + partners: { + list: 'partners list', + get: 'partners get --id ', + }, + populations: { + list: 'populations list', + get: 'populations get --id ', + }, + overlaps: { + list: 'overlaps list [--partner-id ] [--population-id ]', + get: 'overlaps get --id ', + }, + reports: { + list: 'reports list', + get: 'reports get --id ', + }, + threads: { + list: 'threads list', + }, + accounts: { + search: 'accounts search --domain ', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/outreach.js b/tools/clis/outreach.js new file mode 100755 index 0000000..daa7953 --- /dev/null +++ b/tools/clis/outreach.js @@ -0,0 +1,213 @@ +#!/usr/bin/env node + +const ACCESS_TOKEN = process.env.OUTREACH_ACCESS_TOKEN +const BASE_URL = 'https://api.outreach.io/api/v2' + +if (!ACCESS_TOKEN) { + console.error(JSON.stringify({ error: 'OUTREACH_ACCESS_TOKEN environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const url = `${BASE_URL}${path}` + if (args['dry-run']) { + return { _dry_run: true, method, url, headers: { 'Authorization': 'Bearer ***', 'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Authorization': `Bearer ${ACCESS_TOKEN}`, + 'Content-Type': 'application/vnd.api+json', + 'Accept': 'application/vnd.api+json', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'prospects': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args.page) params.set('page[number]', args.page) + if (args['per-page']) params.set('page[size]', args['per-page']) + const qs = params.toString() + result = await api('GET', `/prospects${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/prospects/${id}`) + break + } + case 'create': { + const email = args.email + if (!email) { result = { error: '--email required' }; break } + const attributes = { emails: [email] } + if (args['first-name']) attributes.firstName = args['first-name'] + if (args['last-name']) attributes.lastName = args['last-name'] + const body = { data: { type: 'prospect', attributes } } + result = await api('POST', '/prospects', body) + break + } + default: + result = { error: 'Unknown prospects subcommand. Use: list, get, create' } + } + break + + case 'sequences': + switch (sub) { + case 'list': { + result = await api('GET', '/sequences') + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/sequences/${id}`) + break + } + default: + result = { error: 'Unknown sequences subcommand. Use: list, get' } + } + break + + case 'sequence-states': + switch (sub) { + case 'create': { + const sequenceId = args['sequence-id'] + const prospectId = args['prospect-id'] + if (!sequenceId) { result = { error: '--sequence-id required' }; break } + if (!prospectId) { result = { error: '--prospect-id required' }; break } + const body = { + data: { + type: 'sequenceState', + relationships: { + prospect: { data: { type: 'prospect', id: prospectId } }, + sequence: { data: { type: 'sequence', id: sequenceId } }, + }, + }, + } + result = await api('POST', '/sequenceStates', body) + break + } + default: + result = { error: 'Unknown sequence-states subcommand. Use: create' } + } + break + + case 'mailings': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args['sequence-id']) params.set('filter[sequence][id]', args['sequence-id']) + const qs = params.toString() + result = await api('GET', `/mailings${qs ? '?' + qs : ''}`) + break + } + default: + result = { error: 'Unknown mailings subcommand. Use: list' } + } + break + + case 'accounts': + switch (sub) { + case 'list': { + result = await api('GET', '/accounts') + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/accounts/${id}`) + break + } + default: + result = { error: 'Unknown accounts subcommand. Use: list, get' } + } + break + + case 'tasks': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args.status) params.set('filter[status]', args.status) + const qs = params.toString() + result = await api('GET', `/tasks${qs ? '?' + qs : ''}`) + break + } + default: + result = { error: 'Unknown tasks subcommand. Use: list' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + prospects: { + list: 'prospects list [--page ] [--per-page ]', + get: 'prospects get --id ', + create: 'prospects create --email [--first-name ] [--last-name ]', + }, + sequences: { + list: 'sequences list', + get: 'sequences get --id ', + }, + 'sequence-states': { + create: 'sequence-states create --sequence-id --prospect-id ', + }, + mailings: { + list: 'mailings list [--sequence-id ]', + }, + accounts: { + list: 'accounts list', + get: 'accounts get --id ', + }, + tasks: { + list: 'tasks list [--status ]', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/pendo.js b/tools/clis/pendo.js new file mode 100755 index 0000000..cabd531 --- /dev/null +++ b/tools/clis/pendo.js @@ -0,0 +1,221 @@ +#!/usr/bin/env node + +const API_KEY = process.env.PENDO_INTEGRATION_KEY +const BASE_URL = 'https://app.pendo.io/api/v1' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'PENDO_INTEGRATION_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const url = `${BASE_URL}${path}` + if (args['dry-run']) { + return { _dry_run: true, method, url, headers: { 'Content-Type': 'application/json', 'x-pendo-integration-key': '***' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + 'x-pendo-integration-key': API_KEY, + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'features': + switch (sub) { + case 'list': + result = await api('GET', '/feature') + break + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/feature/${id}`) + break + } + default: + result = { error: 'Unknown features subcommand. Use: list, get' } + } + break + + case 'pages': + switch (sub) { + case 'list': + result = await api('GET', '/page') + break + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/page/${id}`) + break + } + default: + result = { error: 'Unknown pages subcommand. Use: list, get' } + } + break + + case 'guides': + switch (sub) { + case 'list': { + const params = new URLSearchParams() + if (args.state) params.set('state', args.state) + const qs = params.toString() + result = await api('GET', `/guide${qs ? '?' + qs : ''}`) + break + } + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/guide/${id}`) + break + } + default: + result = { error: 'Unknown guides subcommand. Use: list, get' } + } + break + + case 'visitors': + switch (sub) { + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/visitor/${id}`) + break + } + case 'search': { + const query = args.query + if (!query) { result = { error: '--query required' }; break } + let body + try { body = JSON.parse(query) } catch { result = { error: 'Invalid JSON in --query' }; break } + result = await api('POST', '/aggregation', body) + break + } + default: + result = { error: 'Unknown visitors subcommand. Use: get, search' } + } + break + + case 'accounts': + switch (sub) { + case 'get': { + const id = args.id + if (!id) { result = { error: '--id required' }; break } + result = await api('GET', `/account/${id}`) + break + } + case 'search': { + const query = args.query + if (!query) { result = { error: '--query required' }; break } + let body + try { body = JSON.parse(query) } catch { result = { error: 'Invalid JSON in --query' }; break } + result = await api('POST', '/aggregation', body) + break + } + default: + result = { error: 'Unknown accounts subcommand. Use: get, search' } + } + break + + case 'reports': + switch (sub) { + case 'funnel': { + const pipeline = args.pipeline + if (!pipeline) { result = { error: '--pipeline required' }; break } + let body + try { body = JSON.parse(pipeline) } catch { result = { error: 'Invalid JSON in --pipeline' }; break } + result = await api('POST', '/aggregation', body) + break + } + default: + result = { error: 'Unknown reports subcommand. Use: funnel' } + } + break + + case 'metadata': + switch (sub) { + case 'list': { + const kind = args.kind + if (!kind) { result = { error: '--kind required' }; break } + result = await api('GET', `/metadata/schema/${kind}`) + break + } + default: + result = { error: 'Unknown metadata subcommand. Use: list' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + features: { + list: 'features list', + get: 'features get --id ', + }, + pages: { + list: 'pages list', + get: 'pages get --id ', + }, + guides: { + list: 'guides list [--state ]', + get: 'guides get --id ', + }, + visitors: { + get: 'visitors get --id ', + search: 'visitors search --query ', + }, + accounts: { + get: 'accounts get --id ', + search: 'accounts search --query ', + }, + reports: { + funnel: 'reports funnel --pipeline ', + }, + metadata: { + list: 'metadata list --kind ', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/similarweb.js b/tools/clis/similarweb.js new file mode 100755 index 0000000..66ffac6 --- /dev/null +++ b/tools/clis/similarweb.js @@ -0,0 +1,214 @@ +#!/usr/bin/env node + +const API_KEY = process.env.SIMILARWEB_API_KEY +const BASE_URL = 'https://api.similarweb.com/v1' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'SIMILARWEB_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const separator = path.includes('?') ? '&' : '?' + const url = `${BASE_URL}${path}${separator}api_key=${API_KEY}` + if (args['dry-run']) { + return { _dry_run: true, method, url: url.replace(API_KEY, '***'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'traffic': + switch (sub) { + case 'visits': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + if (args.granularity) params.set('granularity', args.granularity) + result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/visits?${params.toString()}`) + break + } + case 'pages-per-visit': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + if (args.granularity) params.set('granularity', args.granularity) + result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/pages-per-visit?${params.toString()}`) + break + } + case 'avg-duration': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + if (args.granularity) params.set('granularity', args.granularity) + result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/average-visit-duration?${params.toString()}`) + break + } + case 'bounce-rate': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + if (args.granularity) params.set('granularity', args.granularity) + result = await api('GET', `/website/${encodeURIComponent(domain)}/total-traffic-and-engagement/bounce-rate?${params.toString()}`) + break + } + case 'sources': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + result = await api('GET', `/website/${encodeURIComponent(domain)}/traffic-sources/overview?${params.toString()}`) + break + } + default: + result = { error: 'Unknown traffic subcommand. Use: visits, pages-per-visit, avg-duration, bounce-rate, sources' } + } + break + + case 'referrals': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + result = await api('GET', `/website/${encodeURIComponent(domain)}/traffic-sources/referrals?${params.toString()}`) + break + } + + case 'search': + switch (sub) { + case 'keywords-organic': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + if (args.limit) params.set('limit', args.limit) + result = await api('GET', `/website/${encodeURIComponent(domain)}/search/organic-search-keywords?${params.toString()}`) + break + } + case 'keywords-paid': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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 }) + if (args.country) params.set('country', args.country) + if (args.limit) params.set('limit', args.limit) + result = await api('GET', `/website/${encodeURIComponent(domain)}/search/paid-search-keywords?${params.toString()}`) + break + } + default: + result = { error: 'Unknown search subcommand. Use: keywords-organic, keywords-paid' } + } + break + + case 'competitors': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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/${encodeURIComponent(domain)}/category-rank/category-rank`) + break + } + + case 'geography': { + const domain = args.domain + if (!domain) { result = { error: '--domain required' }; break } + 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/${encodeURIComponent(domain)}/geo/traffic-by-country?${params.toString()}`) + break + } + + default: + result = { + error: 'Unknown command', + usage: { + traffic: { + visits: 'traffic visits --domain --start --end [--country ] [--granularity ]', + 'pages-per-visit': 'traffic pages-per-visit --domain --start --end ', + 'avg-duration': 'traffic avg-duration --domain --start --end ', + 'bounce-rate': 'traffic bounce-rate --domain --start --end ', + sources: 'traffic sources --domain --start --end ', + }, + referrals: 'referrals --domain --start --end ', + search: { + 'keywords-organic': 'search keywords-organic --domain --start --end ', + 'keywords-paid': 'search keywords-paid --domain --start --end ', + }, + competitors: 'competitors --domain ', + 'category-rank': 'category-rank --domain ', + geography: 'geography --domain --start --end ', + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/supermetrics.js b/tools/clis/supermetrics.js new file mode 100755 index 0000000..26b4165 --- /dev/null +++ b/tools/clis/supermetrics.js @@ -0,0 +1,168 @@ +#!/usr/bin/env node + +const API_KEY = process.env.SUPERMETRICS_API_KEY +const BASE_URL = 'https://api.supermetrics.com/enterprise/v2' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'SUPERMETRICS_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + const separator = path.includes('?') ? '&' : '?' + const url = `${BASE_URL}${path}${separator}api_key=${API_KEY}` + if (args['dry-run']) { + return { _dry_run: true, method, url: url.replace(API_KEY, '***'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: body || undefined } + } + const res = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'query': { + const dsId = args['ds-id'] + const dsAccounts = args['ds-accounts'] + const dateRange = args['date-range'] + const fields = args.fields + if (!dsId) { result = { error: '--ds-id required (e.g., GA4, AW, FB, LI, GSC)' }; break } + if (!dsAccounts) { result = { error: '--ds-accounts required' }; break } + if (!dateRange) { result = { error: '--date-range required (e.g., last_28_days, last_month, this_month, custom)' }; break } + if (!fields) { result = { error: '--fields required (comma-separated field names)' }; break } + const body = { + ds_id: dsId, + ds_accounts: dsAccounts, + date_range_type: dateRange, + fields: fields.split(',').map(f => ({ name: f.trim() })), + } + if (args.filter) body.filter = args.filter + 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/data/json', body) + break + } + + case 'sources': + switch (sub) { + case 'list': + result = await api('GET', '/datasources') + break + default: + result = { error: 'Unknown sources subcommand. Use: list' } + } + break + + case 'accounts': + switch (sub) { + case 'list': { + const dsId = args['ds-id'] + if (!dsId) { result = { error: '--ds-id required (e.g., GA4, AW, FB)' }; break } + const params = new URLSearchParams({ ds_id: dsId }) + result = await api('GET', `/datasources/accounts?${params.toString()}`) + break + } + default: + result = { error: 'Unknown accounts subcommand. Use: list' } + } + break + + case 'teams': + switch (sub) { + case 'list': + result = await api('GET', '/teams') + break + default: + result = { error: 'Unknown teams subcommand. Use: list' } + } + break + + case 'users': + switch (sub) { + case 'list': + result = await api('GET', '/users') + break + default: + result = { error: 'Unknown users subcommand. Use: list' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + query: 'query --ds-id --ds-accounts --date-range --fields [--filter ] [--max-rows ] [--start-date ] [--end-date ]', + sources: { + list: 'sources list', + }, + accounts: { + list: 'accounts list --ds-id ', + }, + teams: { + list: 'teams list', + }, + users: { + list: 'users list', + }, + 'data-source-ids': { + 'GA4': 'Google Analytics 4', + 'GA4_PAID': 'Google Analytics (paid)', + 'AW': 'Google Ads', + 'FB': 'Facebook Ads', + 'LI': 'LinkedIn Ads', + 'TW_ADS': 'Twitter Ads', + 'IG_IA': 'Instagram', + 'FB_IA': 'Facebook Pages', + 'GSC': 'Google Search Console', + 'SE': 'Semrush', + 'MC': 'Mailchimp', + }, + 'date-ranges': ['last_28_days', 'last_month', 'this_month', 'custom'], + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/clis/zoominfo.js b/tools/clis/zoominfo.js new file mode 100755 index 0000000..6ab1aaf --- /dev/null +++ b/tools/clis/zoominfo.js @@ -0,0 +1,216 @@ +#!/usr/bin/env node + +const BASE_URL = 'https://api.zoominfo.com' + +let ACCESS_TOKEN = process.env.ZOOMINFO_ACCESS_TOKEN + +if (!ACCESS_TOKEN && !process.env.ZOOMINFO_USERNAME) { + console.error(JSON.stringify({ error: 'ZOOMINFO_ACCESS_TOKEN or ZOOMINFO_USERNAME + ZOOMINFO_PRIVATE_KEY environment variables required' })) + process.exit(1) +} + +async function authenticate() { + if (ACCESS_TOKEN) return ACCESS_TOKEN + const username = process.env.ZOOMINFO_USERNAME + const password = process.env.ZOOMINFO_PRIVATE_KEY + if (!username || !password) { + throw new Error('ZOOMINFO_USERNAME and ZOOMINFO_PRIVATE_KEY required for authentication') + } + const res = await fetch(`${BASE_URL}/authenticate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + 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 (e) { + if (e.message === 'No JWT in response') throw e + throw new Error(`Authentication failed: ${text}`) + } +} + +async function api(method, path, body) { + if (args['dry-run']) { + return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ***' }, body } + } + const token = await authenticate() + const res = await fetch(`${BASE_URL}${path}`, { + method, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, sub, ...rest] = args._ + +async function main() { + let result + const page = args.page ? Number(args.page) : 1 + const perPage = args['per-page'] ? Number(args['per-page']) : 25 + + 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 + } + + case 'contacts': + switch (sub) { + case 'search': { + const body = { rpp: perPage, page } + if (args['job-title']) body.jobTitle = [args['job-title']] + if (args.company) body.companyName = [args.company] + if (args.location) body.locationSearchType = 'PersonLocation', body.personLocationCity = [args.location] + if (args.seniority) body.managementLevel = [args.seniority] + if (args.department) body.department = [args.department] + result = await api('POST', '/search/contact', body) + break + } + case 'enrich': { + const body = {} + if (args.email) body.matchEmail = [args.email] + if (args['person-id']) body.personId = [args['person-id']] + if (!args.email && !args['person-id']) { + result = { error: '--email or --person-id required' } + break + } + result = await api('POST', '/enrich/contact', body) + break + } + default: + result = { error: 'Unknown contacts subcommand. Use: search, enrich' } + } + break + + case 'companies': + switch (sub) { + case 'search': { + const body = { rpp: perPage, page } + if (args.name) body.companyName = [args.name] + if (args.industry) body.industry = [args.industry] + if (args['revenue-min']) body.revenueMin = Number(args['revenue-min']) + if (args['revenue-max']) body.revenueMax = Number(args['revenue-max']) + if (args['employees-min']) body.employeeCountMin = Number(args['employees-min']) + if (args['employees-max']) body.employeeCountMax = Number(args['employees-max']) + result = await api('POST', '/search/company', body) + break + } + case 'enrich': { + const body = {} + if (args.domain) body.matchCompanyWebsite = [args.domain] + if (args['company-id']) body.companyId = [args['company-id']] + if (!args.domain && !args['company-id']) { + result = { error: '--domain or --company-id required' } + break + } + result = await api('POST', '/enrich/company', body) + break + } + default: + result = { error: 'Unknown companies subcommand. Use: search, enrich' } + } + break + + case 'intent': + switch (sub) { + case 'search': { + if (!args.topic) { + result = { error: '--topic required' } + break + } + const body = { topicId: [args.topic] } + if (args['company-id']) body.companyId = [args['company-id']] + result = await api('POST', '/lookup/intent', body) + break + } + default: + result = { error: 'Unknown intent subcommand. Use: search' } + } + break + + case 'scoops': + switch (sub) { + case 'search': { + const body = { rpp: perPage, page } + if (args['company-id']) body.companyId = [args['company-id']] + if (args.topic) body.topicId = [args.topic] + result = await api('POST', '/lookup/scoops', body) + break + } + default: + result = { error: 'Unknown scoops subcommand. Use: search' } + } + break + + default: + result = { + error: 'Unknown command', + usage: { + auth: 'auth — authenticate and get JWT token', + contacts: { + search: 'contacts search [--job-title ] [--company ] [--location ] [--seniority ] [--department ] [--page ]', + enrich: 'contacts enrich --email | --person-id ', + }, + companies: { + search: 'companies search [--name ] [--industry ] [--revenue-min ] [--employees-min ] [--page ]', + enrich: 'companies enrich --domain | --company-id ', + }, + intent: { + search: 'intent search --topic [--company-id ]', + }, + scoops: { + search: 'scoops search [--company-id ] [--topic ] [--page ]', + }, + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/integrations/airops.md b/tools/integrations/airops.md new file mode 100644 index 0000000..f003bc6 --- /dev/null +++ b/tools/integrations/airops.md @@ -0,0 +1,128 @@ +# AirOps + +AI content platform for crafting content that wins AI search. Build and execute AI workflows (flows) for SEO content generation, data enrichment, and automation. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Flows, Workflows, Runs | +| MCP | - | Not available | +| CLI | ✓ | [airops.js](../clis/airops.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: API Key + Workspace ID +- **Header**: `Authorization: Bearer {api_key}` +- **Env vars**: `AIROPS_API_KEY`, `AIROPS_WORKSPACE_ID` +- **Get key**: Settings > API Keys at https://app.airops.com + +## Common Agent Operations + +### List Flows + +```bash +GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows +``` + +### Get Flow Details + +```bash +GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id} +``` + +### Execute a Flow + +```bash +POST https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}/execute + +{ + "inputs": { + "keyword": "best project management tools", + "target_audience": "startup founders" + } +} +``` + +### List Runs for a Flow + +```bash +GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/flows/{flow_id}/runs +``` + +### Get Run Status + +```bash +GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/runs/{run_id} +``` + +### List Workflows + +```bash +GET https://api.airops.com/public_api/v1/workspaces/{workspace_id}/workflows +``` + +### Execute a Workflow + +```bash +POST https://api.airops.com/public_api/v1/workspaces/{workspace_id}/workflows/{workflow_id}/execute + +{ + "inputs": { + "topic": "email marketing best practices", + "content_type": "blog_post" + } +} +``` + +## Key Metrics + +### Flow Data +- `id` - Flow identifier +- `name` - Flow name +- `description` - Flow description +- `status` - Active/inactive status +- `created_at` - Creation timestamp +- `updated_at` - Last modified timestamp + +### Run Data +- `id` - Run identifier +- `flow_id` - Parent flow ID +- `status` - pending, running, completed, failed +- `inputs` - Input parameters used +- `outputs` - Generated results +- `started_at` - Run start time +- `completed_at` - Run completion time + +## Parameters + +### Flow Execution +- `inputs` - JSON object of key-value pairs matching the flow's expected inputs +- Input keys vary per flow (e.g., `keyword`, `topic`, `url`, `target_audience`) + +### Workflow Execution +- `inputs` - JSON object of key-value pairs matching the workflow's expected inputs + +## When to Use + +- Bulk content generation for SEO at scale +- SEO-optimized article creation with AI workflows +- Data enrichment pipelines for marketing lists +- Keyword research automation +- Content optimization and rewriting +- Programmatic SEO page generation +- AI-powered content briefs and outlines + +## Rate Limits + +- Rate limits vary by plan +- Concurrent execution limits depend on workspace tier +- Check AirOps dashboard for current usage and limits + +## Relevant Skills + +- ai-seo +- content-strategy +- programmatic-seo +- copywriting diff --git a/tools/integrations/clay.md b/tools/integrations/clay.md new file mode 100644 index 0000000..e0ddd02 --- /dev/null +++ b/tools/integrations/clay.md @@ -0,0 +1,149 @@ +# Clay + +Data enrichment and outbound automation platform for building lead lists with waterfall enrichment across 75+ data providers. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Tables, People Enrichment, Company Enrichment | +| MCP | ✓ | [Claude connector](https://claude.com/connectors/clay) | +| CLI | ✓ | [clay.js](../clis/clay.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: API Key (Bearer token) +- **Header**: `Authorization: Bearer {api_key}` +- **Get key**: Settings > API at https://app.clay.com + +## Common Agent Operations + +### List Tables + +```bash +GET https://api.clay.com/v3/tables + +Authorization: Bearer {api_key} +``` + +### Get Table Details + +```bash +GET https://api.clay.com/v3/tables/{table_id} + +Authorization: Bearer {api_key} +``` + +### Get Table Rows + +```bash +GET https://api.clay.com/v3/tables/{table_id}/rows?page=1&per_page=25 + +Authorization: Bearer {api_key} +``` + +### Add Row to Table + +```bash +POST https://api.clay.com/v3/tables/{table_id}/rows + +{ + "first_name": "Jane", + "last_name": "Doe", + "company": "Acme Inc", + "email": "jane@acme.com" +} +``` + +### People Enrichment + +```bash +POST https://api.clay.com/v3/people/enrich + +{ + "email": "jane@acme.com" +} +``` + +### Company Enrichment + +```bash +POST https://api.clay.com/v3/companies/enrich + +{ + "domain": "acme.com" +} +``` + +## Key Metrics + +### Person Data +- `first_name`, `last_name` - Name +- `email` - Email address +- `title` - Job title +- `linkedin_url` - LinkedIn profile +- `company` - Company name +- `location` - Location +- `seniority` - Seniority level + +### Company Data +- `name` - Company name +- `domain` - Website domain +- `industry` - Industry +- `employee_count` - Number of employees +- `revenue` - Estimated revenue +- `location` - Headquarters location +- `technologies` - Tech stack +- `description` - Company description + +### Table Data +- `id` - Table ID +- `name` - Table name +- `row_count` - Number of rows +- `columns` - Column definitions +- `created_at` - Creation timestamp +- `updated_at` - Last update timestamp + +## Parameters + +### Tables +- `page` - Page number (default: 1) +- `per_page` - Results per page (default: 25) + +### People Enrichment +- `email` - Email address +- `linkedin_url` - LinkedIn profile URL +- `first_name` + `last_name` - Name-based lookup + +### Company Enrichment +- `domain` - Company domain (e.g., "acme.com") + +### Add Row +- Fields are dynamic and match the table's column definitions +- Pass data as key-value pairs matching column names + +## When to Use + +- Building enriched prospect lists with waterfall enrichment across multiple providers +- Enriching leads with person and company data from 75+ sources +- Automating outbound workflows with enriched data +- Finding verified contact info (emails, phone numbers, social profiles) +- Company research and firmographic analysis +- Triggering enrichment workflows via webhooks +- Syncing enriched data back to CRM or outbound tools + +## Rate Limits + +- Rate limits vary by plan +- Standard: 100 requests/minute +- Enterprise plans have higher limits +- Enrichment credits consumed per lookup vary by data provider +- Webhook endpoints accept data continuously + +## Relevant Skills + +- cold-email +- revops +- sales-enablement +- competitor-alternatives diff --git a/tools/integrations/close.md b/tools/integrations/close.md new file mode 100644 index 0000000..b32c386 --- /dev/null +++ b/tools/integrations/close.md @@ -0,0 +1,191 @@ +# Close + +Sales CRM for SMBs with built-in calling, email, and pipeline management designed for high-velocity sales teams. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Leads, Contacts, Opportunities, Activities, Tasks | +| MCP | - | Not available | +| CLI | ✓ | [close.js](../clis/close.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: Basic Auth +- **Header**: `Authorization: Basic {base64(api_key + ':')}` +- **Get key**: Settings > API Keys at https://app.close.com + +## Common Agent Operations + +### List Leads + +```bash +GET https://api.close.com/api/v1/lead/ + +Authorization: Basic {base64(api_key + ':')} +``` + +### Search Leads + +```bash +GET https://api.close.com/api/v1/lead/?query=company_name + +Authorization: Basic {base64(api_key + ':')} +``` + +### Create Lead + +```bash +POST https://api.close.com/api/v1/lead/ + +{ + "name": "Acme Corp", + "url": "https://acme.com", + "description": "Enterprise prospect" +} +``` + +### Get Contact + +```bash +GET https://api.close.com/api/v1/contact/{contact_id}/ + +Authorization: Basic {base64(api_key + ':')} +``` + +### Create Contact + +```bash +POST https://api.close.com/api/v1/contact/ + +{ + "lead_id": "lead_xxx", + "name": "Jane Smith", + "emails": [{ "email": "jane@acme.com", "type": "office" }], + "phones": [{ "phone": "+15551234567", "type": "office" }] +} +``` + +### Create Opportunity + +```bash +POST https://api.close.com/api/v1/opportunity/ + +{ + "lead_id": "lead_xxx", + "value": 50000, + "status_type": "active" +} +``` + +### List Activities + +```bash +GET https://api.close.com/api/v1/activity/?lead_id=lead_xxx + +Authorization: Basic {base64(api_key + ':')} +``` + +### Create Task + +```bash +POST https://api.close.com/api/v1/task/ + +{ + "lead_id": "lead_xxx", + "text": "Follow up on demo request", + "_type": "lead", + "date": "2026-03-10" +} +``` + +## Key Metrics + +### Lead Data +- `id` - Lead ID +- `display_name` - Lead name +- `url` - Website URL +- `description` - Lead description +- `status_id` - Pipeline status +- `contacts` - Associated contacts +- `opportunities` - Associated opportunities +- `tasks` - Associated tasks + +### Contact Data +- `id` - Contact ID +- `lead_id` - Parent lead +- `name` - Full name +- `title` - Job title +- `emails` - Email addresses array +- `phones` - Phone numbers array + +### Opportunity Data +- `id` - Opportunity ID +- `lead_id` - Parent lead +- `value` - Value in cents +- `status_type` - active, won, or lost +- `confidence` - Win probability (0-100) +- `date_won` - Close date + +### Task Data +- `id` - Task ID +- `lead_id` - Parent lead +- `text` - Task description +- `assigned_to` - Assigned user ID +- `date` - Due date +- `is_complete` - Completion status + +## Parameters + +### Leads +- `query` - Search query string +- `_skip` - Number of results to skip (pagination) +- `_limit` - Max results to return (default: 100) +- `_fields` - Comma-separated fields to return + +### Contacts +- `lead_id` - Filter by parent lead +- `_skip` - Pagination offset +- `_limit` - Max results + +### Opportunities +- `lead_id` - Filter by parent lead +- `status` - Filter by status type (active, won, lost) +- `_skip` - Pagination offset +- `_limit` - Max results + +### Activities +- `lead_id` - Filter by lead +- `_type__type` - Filter by type (Email, Call, Note, SMS, Meeting) +- `date_created__gt` - After date +- `date_created__lt` - Before date + +### Tasks +- `assigned_to` - Filter by user ID +- `is_complete` - Filter by completion (true/false) +- `lead_id` - Filter by lead +- `_type` - Task type (lead) + +## When to Use + +- Managing SMB sales pipelines with high-touch outreach +- Tracking sales activities (calls, emails, meetings) per lead +- Creating and managing tasks for sales follow-ups +- Opportunity tracking and revenue forecasting +- Building automated outreach workflows +- Sales team performance reporting + +## Rate Limits + +- Rate limits based on organization plan +- Standard: ~100 requests/minute +- Responses include `ratelimit-limit` and `ratelimit-remaining` headers +- 429 responses include `Retry-After` header + +## Relevant Skills + +- revops +- sales-enablement +- cold-email diff --git a/tools/integrations/coupler.md b/tools/integrations/coupler.md new file mode 100644 index 0000000..700e7b3 --- /dev/null +++ b/tools/integrations/coupler.md @@ -0,0 +1,142 @@ +# Coupler.io + +Data integration platform that connects marketing, sales, analytics, and e-commerce data sources to destinations like spreadsheets, BI tools, and data warehouses with automated scheduling. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Importers, Runs, Sources, Destinations | +| MCP | ✓ | [Claude connector](https://claude.com/connectors/coupler-io) | +| CLI | ✓ | [coupler.js](../clis/coupler.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: API Key +- **Header**: `Authorization: Bearer {api_key}` +- **Get key**: Settings > API at https://app.coupler.io + +## Common Agent Operations + +### List Importers + +```bash +GET https://api.coupler.io/v1/importers +``` + +### Get Importer Details + +```bash +GET https://api.coupler.io/v1/importers/{id} +``` + +### Trigger an Importer Run + +```bash +POST https://api.coupler.io/v1/importers/{id}/run +``` + +### Create an Importer + +```bash +POST https://api.coupler.io/v1/importers + +{ + "source_type": "google_analytics", + "destination_type": "google_sheets", + "name": "GA4 to Sheets Daily" +} +``` + +### Delete an Importer + +```bash +DELETE https://api.coupler.io/v1/importers/{id} +``` + +### List Runs for an Importer + +```bash +GET https://api.coupler.io/v1/importers/{id}/runs +``` + +### Get Run Details + +```bash +GET https://api.coupler.io/v1/runs/{id} +``` + +### List Available Sources + +```bash +GET https://api.coupler.io/v1/sources +``` + +### List Available Destinations + +```bash +GET https://api.coupler.io/v1/destinations +``` + +## Key Metrics + +### Importer Data +- `id` - Importer ID +- `name` - Importer name +- `source_type` - Source connector type +- `destination_type` - Destination connector type +- `schedule` - Automation schedule +- `status` - Current status +- `last_run_at` - Last run timestamp + +### Run Data +- `id` - Run ID +- `importer_id` - Parent importer +- `status` - Run status (pending, running, completed, failed) +- `started_at` - Start timestamp +- `finished_at` - Finish timestamp +- `rows_imported` - Number of rows processed +- `error` - Error message if failed + +## Parameters + +### Importer Creation +- `source_type` - Source connector (e.g., google_analytics, google_ads, facebook_ads, hubspot, shopify, stripe, airtable) +- `destination_type` - Destination connector (e.g., google_sheets, bigquery, snowflake, postgresql) +- `name` - Importer name +- `schedule` - Automation schedule (e.g., hourly, daily, weekly) + +### Supported Sources +- **Analytics**: Google Analytics, Adobe Analytics +- **Ads**: Google Ads, Facebook Ads, LinkedIn Ads, TikTok Ads +- **CRM**: HubSpot, Salesforce, Pipedrive +- **E-commerce**: Shopify, Stripe, WooCommerce +- **Other**: Airtable, Google Sheets, BigQuery, MySQL, PostgreSQL + +### Supported Destinations +- **Spreadsheets**: Google Sheets, Excel Online +- **BI Tools**: Looker Studio, Power BI, Tableau +- **Data Warehouses**: BigQuery, Snowflake, Redshift +- **Databases**: PostgreSQL, MySQL + +## When to Use + +- Automating marketing data pipelines from ads and analytics platforms +- Consolidating multi-channel campaign data into a single destination +- Scheduling recurring data syncs from CRM to spreadsheets or BI tools +- Building marketing dashboards with fresh data from multiple sources +- Exporting e-commerce data for reporting and analysis +- Connecting data sources without writing custom ETL code + +## Rate Limits + +- Rate limits vary by plan +- Standard: API access available on Professional and higher plans +- Importer run frequency depends on plan tier + +## Relevant Skills + +- analytics-tracking +- paid-ads +- revops diff --git a/tools/integrations/crossbeam.md b/tools/integrations/crossbeam.md new file mode 100644 index 0000000..5eb06cd --- /dev/null +++ b/tools/integrations/crossbeam.md @@ -0,0 +1,137 @@ +# Crossbeam + +Partner ecosystem platform (now part of Reveal) for sharing account data with partners to identify co-sell opportunities, overlapping customers, and partner-sourced pipeline. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Partners, Populations, Overlaps, Reports, Threads | +| MCP | ✓ | [Claude connector](https://claude.com/connectors/crossbeam) | +| CLI | ✓ | [crossbeam.js](../clis/crossbeam.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: API Key +- **Header**: `Authorization: Bearer {api_key}` +- **Get key**: Settings > API at https://app.crossbeam.com + +## Common Agent Operations + +### List Partners + +```bash +GET https://api.crossbeam.com/v1/partners +Authorization: Bearer {api_key} +``` + +### Get Partner Details + +```bash +GET https://api.crossbeam.com/v1/partners/{id} +Authorization: Bearer {api_key} +``` + +### List Populations + +```bash +GET https://api.crossbeam.com/v1/populations +Authorization: Bearer {api_key} +``` + +### List Overlaps + +```bash +GET https://api.crossbeam.com/v1/overlaps?partner_id={partner_id}&population_id={population_id} +Authorization: Bearer {api_key} +``` + +### Get Overlap Details + +```bash +GET https://api.crossbeam.com/v1/overlaps/{id} +Authorization: Bearer {api_key} +``` + +### Search Accounts + +```bash +GET https://api.crossbeam.com/v1/accounts/search?domain={domain} +Authorization: Bearer {api_key} +``` + +### List Reports + +```bash +GET https://api.crossbeam.com/v1/reports +Authorization: Bearer {api_key} +``` + +### List Collaboration Threads + +```bash +GET https://api.crossbeam.com/v1/threads +Authorization: Bearer {api_key} +``` + +## Key Metrics + +### Partner Data +- `id` - Partner ID +- `name` - Partner company name +- `status` - Partnership status (active, pending, etc.) +- `created_at` - When the partnership was established +- `populations_shared` - Number of shared populations + +### Population Data +- `id` - Population ID +- `name` - Population name (e.g., "Customers", "Open Opportunities") +- `record_count` - Number of records in population +- `partner_visibility` - What partners can see + +### Overlap Data +- `id` - Overlap ID +- `partner_id` - Partner involved +- `population_id` - Population matched +- `account_name` - Overlapping account name +- `overlap_type` - Type of overlap (customer, prospect, etc.) +- `match_confidence` - Match confidence score + +### Report Data +- `id` - Report ID +- `name` - Report name +- `type` - Report type +- `created_at` - Creation date +- `results` - Report results data + +## Parameters + +### Overlaps List +- `partner_id` - Filter by specific partner +- `population_id` - Filter by specific population + +### Accounts Search +- `domain` - Company domain to search for + +## When to Use + +- Identifying co-sell opportunities with channel partners +- Finding overlapping customers and prospects across partner ecosystems +- Building partner-sourced pipeline by matching accounts +- Tracking partner influence on deals +- Creating account mapping reports for partner meetings +- Prioritizing which partners to engage based on overlap data + +## Rate Limits + +- Rate limits vary by plan +- Standard: 100 requests/minute +- Pagination supported on list endpoints + +## Relevant Skills + +- revops +- sales-enablement +- referral-program +- competitor-alternatives diff --git a/tools/integrations/outreach.md b/tools/integrations/outreach.md new file mode 100644 index 0000000..7596760 --- /dev/null +++ b/tools/integrations/outreach.md @@ -0,0 +1,172 @@ +# Outreach + +Sales engagement platform for managing prospects, sequences, and outbound campaigns at scale. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Prospects, Sequences, Mailings, Accounts, Tasks | +| MCP | ✓ | [Claude connector](https://claude.com/connectors/outreach) | +| CLI | ✓ | [outreach.js](../clis/outreach.js) | +| SDK | - | REST API only (JSON:API format) | + +## Authentication + +- **Type**: OAuth2 Bearer Token +- **Header**: `Authorization: Bearer {access_token}` +- **Content-Type**: `application/vnd.api+json` +- **Get token**: Settings > API at https://app.outreach.io or via OAuth2 flow + +## Common Agent Operations + +### List Prospects + +```bash +curl -s https://api.outreach.io/api/v2/prospects \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" +``` + +### Get a Prospect + +```bash +curl -s https://api.outreach.io/api/v2/prospects/42 \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" +``` + +### Create a Prospect + +```bash +curl -s -X POST https://api.outreach.io/api/v2/prospects \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "prospect", + "attributes": { + "emails": ["jane@example.com"], + "firstName": "Jane", + "lastName": "Doe" + } + } + }' +``` + +### List Sequences + +```bash +curl -s https://api.outreach.io/api/v2/sequences \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" +``` + +### Add Prospect to Sequence + +```bash +curl -s -X POST https://api.outreach.io/api/v2/sequenceStates \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + -d '{ + "data": { + "type": "sequenceState", + "relationships": { + "prospect": { "data": { "type": "prospect", "id": 42 } }, + "sequence": { "data": { "type": "sequence", "id": 7 } } + } + } + }' +``` + +### List Mailings for a Sequence + +```bash +curl -s "https://api.outreach.io/api/v2/mailings?filter[sequence][id]=7" \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" +``` + +### List Accounts + +```bash +curl -s https://api.outreach.io/api/v2/accounts \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" +``` + +### List Tasks + +```bash +curl -s "https://api.outreach.io/api/v2/tasks?filter[status]=incomplete" \ + -H "Authorization: Bearer $OUTREACH_ACCESS_TOKEN" \ + -H "Content-Type: application/vnd.api+json" +``` + +## Key Metrics + +### Prospect Data +- `firstName`, `lastName` - Name +- `emails` - Email addresses +- `title` - Job title +- `company` - Company name +- `tags` - Prospect tags +- `engagedAt` - Last engagement timestamp + +### Sequence Data +- `name` - Sequence name +- `enabled` - Whether sequence is active +- `sequenceType` - Type (e.g., interval, date-based) +- `stepCount` - Number of steps +- `openCount`, `clickCount`, `replyCount` - Engagement metrics + +### Mailing Data +- `mailingType` - Type of mailing +- `state` - Delivery state +- `openCount`, `clickCount` - Engagement +- `deliveredAt`, `openedAt`, `clickedAt` - Timestamps + +## Parameters + +### Prospects +- `page[number]` - Page number (default: 1) +- `page[size]` - Results per page (default: 25, max: 1000) +- `filter[emails]` - Filter by email +- `filter[firstName]` - Filter by first name +- `filter[lastName]` - Filter by last name +- `sort` - Sort field (e.g., `createdAt`, `-updatedAt`) + +### Sequences +- `filter[name]` - Filter by sequence name +- `filter[enabled]` - Filter by active status + +### Mailings +- `filter[sequence][id]` - Filter by sequence ID +- `filter[prospect][id]` - Filter by prospect ID + +### Tasks +- `filter[status]` - Filter by status (e.g., `incomplete`, `complete`) +- `filter[taskType]` - Filter by type (e.g., `call`, `email`, `action_item`) + +## When to Use + +- Managing outbound sales sequences and cadences +- Adding prospects to automated email sequences +- Tracking prospect engagement across touchpoints +- Managing sales tasks and follow-ups +- Coordinating multi-channel outreach campaigns +- Monitoring sequence performance and reply rates + +## Rate Limits + +- 10,000 requests per hour per user +- Burst limit: 100 requests per 10 seconds +- Rate limit headers returned: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` +- 429 responses when limits exceeded + +## Relevant Skills + +- cold-email +- revops +- sales-enablement +- email-sequence diff --git a/tools/integrations/pendo.md b/tools/integrations/pendo.md new file mode 100644 index 0000000..a50d953 --- /dev/null +++ b/tools/integrations/pendo.md @@ -0,0 +1,208 @@ +# Pendo + +Product analytics and in-app guidance platform for tracking user behavior, measuring feature adoption, and delivering targeted in-app messages. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Features, Pages, Guides, Visitors, Accounts, Reports, Metadata | +| MCP | - | Not available | +| CLI | ✓ | [pendo.js](../clis/pendo.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: Integration Key +- **Header**: `x-pendo-integration-key: {key}` +- **Get key**: Settings > Integrations at https://app.pendo.io + +## Common Agent Operations + +### List Features + +```bash +GET https://app.pendo.io/api/v1/feature +``` + +### Get Feature Details + +```bash +GET https://app.pendo.io/api/v1/feature/{featureId} +``` + +### List Pages + +```bash +GET https://app.pendo.io/api/v1/page +``` + +### Get Page Details + +```bash +GET https://app.pendo.io/api/v1/page/{pageId} +``` + +### List Guides + +```bash +GET https://app.pendo.io/api/v1/guide?state=public +``` + +### Get Guide Details + +```bash +GET https://app.pendo.io/api/v1/guide/{guideId} +``` + +### Get Visitor Data + +```bash +GET https://app.pendo.io/api/v1/visitor/{visitorId} +``` + +### Search Visitors + +```bash +POST https://app.pendo.io/api/v1/aggregation + +{ + "response": { "mimeType": "application/json" }, + "request": { + "pipeline": [ + { "source": { "visitors": null } }, + { "filter": "lastVisitedAt > 1700000000000" } + ] + } +} +``` + +### Get Account Data + +```bash +GET https://app.pendo.io/api/v1/account/{accountId} +``` + +### Search Accounts + +```bash +POST https://app.pendo.io/api/v1/aggregation + +{ + "response": { "mimeType": "application/json" }, + "request": { + "pipeline": [ + { "source": { "accounts": null } }, + { "filter": "metadata.auto.lastupdated > 1700000000000" } + ] + } +} +``` + +### Run Funnel Report + +```bash +POST https://app.pendo.io/api/v1/aggregation + +{ + "response": { "mimeType": "application/json" }, + "request": { + "pipeline": [ + { "source": { "visitors": null, "timeSeries": { "period": "dayRange", "first": 1700000000000, "last": 1700600000000 } } }, + { "identified": "visitorId" }, + { "filter": "pageId == \"page-id-1\"" }, + { "filter": "pageId == \"page-id-2\"" } + ] + } +} +``` + +### List Metadata Fields + +```bash +GET https://app.pendo.io/api/v1/metadata/schema/visitor +GET https://app.pendo.io/api/v1/metadata/schema/account +GET https://app.pendo.io/api/v1/metadata/schema/parentAccount +``` + +## Key Metrics + +### Feature Data +- `id` - Feature ID +- `name` - Feature name +- `kind` - Feature type +- `elementPath` - CSS selector for the tracked element +- `pageId` - Associated page ID +- `numEvents` - Event count +- `numVisitors` - Unique visitor count + +### Page Data +- `id` - Page ID +- `name` - Page name +- `rules` - URL matching rules +- `numEvents` - Pageview count +- `numVisitors` - Unique visitor count + +### Guide Data +- `id` - Guide ID +- `name` - Guide name +- `state` - Guide state (draft, staged, public, disabled) +- `launchMethod` - How the guide is triggered +- `steps` - Guide step definitions +- `numSteps` - Number of steps +- `numViews` - Total views +- `numVisitors` - Unique visitors who saw the guide + +### Visitor Data +- `visitorId` - Unique visitor identifier +- `lastVisitedAt` - Last visit timestamp +- `firstVisit` - First visit timestamp +- `numEvents` - Total event count +- `metadata` - Custom visitor metadata + +### Account Data +- `accountId` - Unique account identifier +- `lastVisitedAt` - Last visit from any account member +- `numVisitors` - Number of visitors in the account +- `metadata` - Custom account metadata + +## Parameters + +### Guide Filtering +- `state` - Filter by state: draft, staged, public, disabled + +### Aggregation Queries +- `source` - Data source: visitors, accounts, features, pages, guides +- `filter` - Expression-based filtering +- `sort` - Sort results +- `limit` - Max results to return +- `timeSeries` - Time range with period, first, last + +### Metadata Kinds +- `visitor` - Visitor metadata schema +- `account` - Account metadata schema +- `parentAccount` - Parent account metadata schema + +## When to Use + +- Tracking feature adoption and usage patterns +- Building and managing in-app onboarding guides +- Analyzing user behavior across pages and features +- Segmenting users by engagement level +- Running funnel analysis on user journeys +- Identifying at-risk accounts based on usage decline +- A/B testing in-app messages and tooltips + +## Rate Limits + +- Rate limits vary by plan +- Standard: 500 requests per minute +- Aggregation queries: may take longer for large datasets +- Use pagination for large result sets + +## Relevant Skills + +- analytics-tracking +- onboarding-cro +- churn-prevention +- ab-test-setup diff --git a/tools/integrations/similarweb.md b/tools/integrations/similarweb.md new file mode 100644 index 0000000..02b4fba --- /dev/null +++ b/tools/integrations/similarweb.md @@ -0,0 +1,150 @@ +# Similarweb + +Competitive traffic intelligence platform providing website analytics, traffic sources, keyword data, and competitor insights. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Traffic, Search, Referrals, Competitors, Geography | +| MCP | - | Not available | +| CLI | ✓ | [similarweb.js](../clis/similarweb.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: API Key +- **Query param**: `?api_key={key}` +- **Get key**: Account Settings > API at https://account.similarweb.com + +## Common Agent Operations + +### Total Visits + +```bash +GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/visits?api_key={key}&start_date=2024-01&end_date=2024-03&country=us&granularity=monthly +``` + +### Pages Per Visit + +```bash +GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/pages-per-visit?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Average Visit Duration + +```bash +GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/average-visit-duration?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Bounce Rate + +```bash +GET https://api.similarweb.com/v1/website/example.com/total-traffic-and-engagement/bounce-rate?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Traffic Sources Breakdown + +```bash +GET https://api.similarweb.com/v1/website/example.com/traffic-sources/overview?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Top Referral Sites + +```bash +GET https://api.similarweb.com/v1/website/example.com/traffic-sources/referrals?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Organic Keywords + +```bash +GET https://api.similarweb.com/v1/website/example.com/search/organic-search-keywords?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Paid Keywords + +```bash +GET https://api.similarweb.com/v1/website/example.com/search/paid-search-keywords?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +### Similar Sites / Competitors + +```bash +GET https://api.similarweb.com/v1/website/example.com/similar-sites/similarsites?api_key={key} +``` + +### Category Ranking + +```bash +GET https://api.similarweb.com/v1/website/example.com/category-rank/category-rank?api_key={key} +``` + +### Traffic by Country + +```bash +GET https://api.similarweb.com/v1/website/example.com/geo/traffic-by-country?api_key={key}&start_date=2024-01&end_date=2024-03 +``` + +## Key Metrics + +### Traffic & Engagement +- `visits` - Total visits for the period +- `pages_per_visit` - Average pages viewed per visit +- `average_visit_duration` - Average session duration in seconds +- `bounce_rate` - Percentage of single-page visits + +### Traffic Sources +- `search` - Organic + paid search percentage +- `social` - Social media traffic percentage +- `direct` - Direct traffic percentage +- `referrals` - Referral traffic percentage +- `mail` - Email traffic percentage +- `display_ads` - Display advertising percentage + +### Search Keywords +- `search_term` - Keyword text +- `share` - Traffic share percentage +- `volume` - Search volume +- `cpc` - Cost per click +- `position` - Average ranking position + +### Geography +- `country` - Country code +- `share` - Traffic share from that country + +## Parameters + +### Common Parameters +- `start_date` - Start month (YYYY-MM format) +- `end_date` - End month (YYYY-MM format) +- `country` - Two-letter country code (e.g., us, gb, de) +- `granularity` - Data granularity: monthly, weekly, daily + +### Search Parameters +- `limit` - Number of keywords to return +- `country` - Filter by country + +## When to Use + +- Analyzing competitor website traffic and engagement metrics +- Benchmarking your site against competitors +- Identifying top traffic sources for any website +- Discovering competitor organic and paid keywords +- Finding similar sites and competitive landscape +- Understanding geographic traffic distribution +- Auditing SEO performance relative to competitors +- Researching market share by traffic volume + +## Rate Limits + +- Rate limits vary by plan tier +- Standard: 10 requests/second +- Data availability depends on plan (3 months to 36 months historical) +- Some endpoints require Premium or Enterprise plans + +## Relevant Skills + +- seo-audit +- competitor-alternatives +- paid-ads +- content-strategy diff --git a/tools/integrations/supermetrics.md b/tools/integrations/supermetrics.md new file mode 100644 index 0000000..25aa25f --- /dev/null +++ b/tools/integrations/supermetrics.md @@ -0,0 +1,145 @@ +# Supermetrics + +Marketing data pipeline that connects 200+ marketing platforms. Pulls data from ad platforms, analytics, social, SEO, email, and more into a single query interface. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Query any connected data source, manage accounts | +| MCP | ✓ | [Claude connector](https://claude.com/connectors/supermetrics) | +| CLI | ✓ | [supermetrics.js](../clis/supermetrics.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: API Key +- **Query param**: `api_key={api_key}` or **Header**: `x-api-key: {api_key}` +- **Get key**: Supermetrics Hub > API settings at https://hub.supermetrics.com + +## Common Agent Operations + +### Query a Data Source + +```bash +POST https://api.supermetrics.com/enterprise/v2/query/data/json + +{ + "ds_id": "GA4", + "ds_accounts": "123456789", + "date_range_type": "last_28_days", + "fields": [ + { "name": "sessions" }, + { "name": "pageviews" }, + { "name": "date" } + ] +} +``` + +### Query with Filters + +```bash +POST https://api.supermetrics.com/enterprise/v2/query/data/json + +{ + "ds_id": "AW", + "ds_accounts": "123-456-7890", + "date_range_type": "last_month", + "fields": [ + { "name": "campaign_name" }, + { "name": "clicks" }, + { "name": "impressions" }, + { "name": "cost" } + ], + "max_rows": 100 +} +``` + +### List Available Data Sources + +```bash +GET https://api.supermetrics.com/enterprise/v2/datasources +``` + +### List Connected Accounts + +```bash +GET https://api.supermetrics.com/enterprise/v2/datasources/accounts?ds_id=GA4 +``` + +### List Teams + +```bash +GET https://api.supermetrics.com/enterprise/v2/teams +``` + +### List Users + +```bash +GET https://api.supermetrics.com/enterprise/v2/users +``` + +## Key Metrics + +### Data Source IDs +- `GA4` - Google Analytics 4 +- `GA4_PAID` - Google Analytics (paid) +- `AW` - Google Ads +- `FB` - Facebook Ads +- `LI` - LinkedIn Ads +- `TW_ADS` - Twitter Ads +- `IG_IA` - Instagram +- `FB_IA` - Facebook Pages +- `GSC` - Google Search Console +- `SE` - Semrush +- `MC` - Mailchimp +- `HubSpot` - HubSpot + +### Date Range Values +- `last_28_days` - Last 28 days +- `last_month` - Previous calendar month +- `this_month` - Current month to date +- `custom` - Custom range (requires `start_date` and `end_date`) + +## Parameters + +### Query +- `ds_id` - Data source identifier (required) +- `ds_accounts` - Account ID for the data source (required) +- `date_range_type` - Date range preset or "custom" (required) +- `fields` - Array of field objects with `name` property (required) +- `filter` - Filter expression for narrowing results +- `max_rows` - Maximum number of rows to return +- `start_date` - Start date for custom range (YYYY-MM-DD) +- `end_date` - End date for custom range (YYYY-MM-DD) + +### Common Fields by Source +- **GA4**: `sessions`, `pageviews`, `users`, `bounce_rate`, `date`, `source`, `medium`, `page_path` +- **Google Ads**: `campaign_name`, `clicks`, `impressions`, `cost`, `conversions`, `ctr`, `cpc` +- **Facebook Ads**: `campaign_name`, `impressions`, `clicks`, `spend`, `reach`, `cpm`, `cpc` +- **LinkedIn Ads**: `campaign_name`, `impressions`, `clicks`, `cost`, `conversions` +- **GSC**: `query`, `clicks`, `impressions`, `ctr`, `position`, `page` + +## When to Use + +- Pulling cross-platform marketing data into a single report +- Comparing performance across ad platforms (Google, Meta, LinkedIn, TikTok) +- Aggregating analytics data from multiple sources +- Automating marketing reporting workflows +- Building unified dashboards across marketing channels +- Extracting SEO data alongside paid media metrics + +## Rate Limits + +- Rate limits vary by plan +- Enterprise API: typically 100 requests/minute +- Query results may be paginated for large datasets +- Recommended: use `max_rows` to control response size + +## Relevant Skills + +- analytics-tracking +- paid-ads +- seo-audit +- content-strategy +- social-content diff --git a/tools/integrations/zoominfo.md b/tools/integrations/zoominfo.md new file mode 100644 index 0000000..06ad773 --- /dev/null +++ b/tools/integrations/zoominfo.md @@ -0,0 +1,191 @@ +# ZoomInfo + +B2B contact database and intent data platform with 100M+ business contacts and company intelligence for sales and marketing teams. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Contact Search, Company Search, Enrichment, Intent Data, Scoops | +| MCP | ✓ | [Claude connector](https://claude.com/connectors/zoominfo) | +| CLI | ✓ | [zoominfo.js](../clis/zoominfo.js) | +| SDK | - | REST API only | + +## Authentication + +- **Type**: JWT Token (Bearer) +- **Flow**: POST `/authenticate` with username + password, receive JWT token +- **Header**: `Authorization: Bearer {jwt_token}` +- **Env vars**: `ZOOMINFO_USERNAME` + `ZOOMINFO_PRIVATE_KEY` or `ZOOMINFO_ACCESS_TOKEN` +- **Get credentials**: Contact ZoomInfo sales or admin portal at https://app.zoominfo.com + +## Common Agent Operations + +### Authenticate + +```bash +POST https://api.zoominfo.com/authenticate + +{ + "username": "user@company.com", + "password": "private-key-here" +} +``` + +### Contact Search + +```bash +POST https://api.zoominfo.com/search/contact + +{ + "jobTitle": ["VP Marketing"], + "companyName": ["Acme Corp"], + "managementLevel": ["VP"], + "rpp": 25, + "page": 1 +} +``` + +### Contact Enrichment + +```bash +POST https://api.zoominfo.com/enrich/contact + +{ + "matchEmail": ["jane@acme.com"] +} +``` + +### Company Search + +```bash +POST https://api.zoominfo.com/search/company + +{ + "companyName": ["Acme"], + "industry": ["Software"], + "employeeCountMin": 50, + "revenueMin": 10000000, + "rpp": 25, + "page": 1 +} +``` + +### Company Enrichment + +```bash +POST https://api.zoominfo.com/enrich/company + +{ + "matchCompanyWebsite": ["acme.com"] +} +``` + +### Intent Data Lookup + +```bash +POST https://api.zoominfo.com/lookup/intent + +{ + "topicId": ["marketing-automation"], + "companyId": ["123456"] +} +``` + +### Scoops Lookup + +```bash +POST https://api.zoominfo.com/lookup/scoops + +{ + "companyId": ["123456"], + "rpp": 25, + "page": 1 +} +``` + +## Key Metrics + +### Contact Data +- `firstName`, `lastName` - Name +- `jobTitle` - Job title +- `email` - Verified email +- `phone` - Direct phone +- `linkedinUrl` - LinkedIn profile +- `companyName` - Company name +- `managementLevel` - Seniority level +- `department` - Department + +### Company Data +- `companyName` - Company name +- `website` - Website URL +- `employeeCount` - Employee count +- `industry` - Industry +- `revenue` - Annual revenue +- `techStack` - Technologies used +- `fundingAmount` - Total funding +- `companyCity`, `companyState`, `companyCountry` - Location + +### Intent Data +- `topicName` - Intent topic +- `signalScore` - Signal strength +- `audienceStrength` - Audience engagement level +- `firstSeenDate`, `lastSeenDate` - Signal timeframe + +## Parameters + +### Contact Search +- `jobTitle` - Array of job titles +- `companyName` - Array of company names +- `managementLevel` - Array: C-Level, VP, Director, Manager, Staff +- `department` - Array: Marketing, Sales, Engineering, Finance, etc. +- `personLocationCity` - Array of cities +- `personLocationState` - Array of states +- `personLocationCountry` - Array of countries +- `rpp` - Results per page (default: 25, max: 100) +- `page` - Page number (default: 1) + +### Contact Enrichment +- `matchEmail` - Array of email addresses +- `personId` - Array of ZoomInfo person IDs +- `matchFirstName` + `matchLastName` + `matchCompanyName` - Alternative lookup + +### Company Search +- `companyName` - Array of company names +- `industry` - Array of industries +- `employeeCountMin` / `employeeCountMax` - Employee count range +- `revenueMin` / `revenueMax` - Revenue range +- `companyLocationCity` - Array of cities +- `rpp` - Results per page +- `page` - Page number + +### Company Enrichment +- `matchCompanyWebsite` - Array of domains +- `companyId` - Array of ZoomInfo company IDs + +### Intent Data +- `topicId` - Array of intent topic IDs +- `companyId` - Array of company IDs + +## When to Use + +- Identifying in-market accounts with intent signals +- Building targeted contact lists by role, seniority, and company +- Enriching leads with verified contact data and firmographics +- Finding decision-makers at target accounts for ABM +- Tracking company news and leadership changes via scoops +- Prioritizing outreach based on buyer intent signals + +## Rate Limits + +- Rate limits vary by plan and endpoint +- Standard: ~200 requests/minute +- Bulk endpoints: batched requests recommended +- Authentication tokens expire after ~12 hours + +## Relevant Skills + +- cold-email +- revops +- sales-enablement +- competitor-alternatives