> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vortexiq.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Keap (Infusionsoft) on Vortex IQ

> Monitor Keap (Infusionsoft) pipeline, responsiveness and customer-satisfaction signals, and catch stalls before they become churn.

export const CapabilityPage = ({data}) => {
  const d = data || ({});
  const [tab, setTab] = useState('overview');
  const [sel, setSel] = useState(null);
  const [q, setQ] = useState('');
  const [outcome, setOutcome] = useState('');
  const [status, setStatus] = useState('');
  const [limit, setLimit] = useState(8);
  const monitor = d.monitor || [];
  const audits = d.audits || [];
  const autos = d.autos || [];
  const outcomes = useMemo(() => {
    const s = new Set();
    monitor.forEach(m => s.add(m.o));
    audits.forEach(a => s.add(a.o));
    return [...s].sort();
  }, [monitor, audits]);
  const rows = useMemo(() => {
    const t = q.trim().toLowerCase();
    if (tab === 'monitor') {
      return monitor.filter(m => (!t || (m.n + ' ' + (m.d || '')).toLowerCase().includes(t)) && (!outcome || m.o === outcome));
    }
    if (tab === 'audit') {
      return audits.filter(a => (!t || (a.n + ' ' + (a.why || '')).toLowerCase().includes(t)) && (!outcome || a.o === outcome) && (!status || a.s === status));
    }
    if (tab === 'automate') {
      return autos.filter(w => !t || (w.n + ' ' + (w.d || '')).toLowerCase().includes(t));
    }
    return [];
  }, [tab, q, outcome, status, monitor, audits, autos]);
  const pill = (text, kind) => {
    const tone = kind === 'good' ? {
      color: '#1f9e54',
      borderColor: '#1f9e54',
      background: '#ecf8f110'
    } : kind === 'warn' ? {
      color: '#c07a17',
      borderColor: '#c07a17',
      background: '#fdf6e910'
    } : kind === 'bad' ? {
      color: '#c53527',
      borderColor: '#c53527',
      background: '#fdf0ee10'
    } : kind === 'brand' ? {
      color: '#5529d6',
      borderColor: '#5529d6'
    } : {
      color: 'inherit',
      borderColor: 'currentColor',
      opacity: 0.65
    };
    return <span key={text} style={{
      fontSize: '10.5px',
      fontFamily: 'ui-monospace, monospace',
      border: '1px solid',
      borderRadius: '999px',
      padding: '2px 8px',
      whiteSpace: 'nowrap',
      ...tone
    }}>
        {text}
      </span>;
  };
  const sevKind = s => s === 'critical' ? 'bad' : s === 'high' ? 'warn' : 'muted';
  const fixKind = s => s === 'Prepared fix' ? 'good' : s === 'Candidate remediation' ? 'warn' : 'muted';
  const card = (item, kind) => {
    const title = item.n;
    const badges = kind === 'monitor' ? [pill(item.o, 'brand'), pill(item.a === 'Watch only' || item.a === 'Merchant rule' ? item.a : `Alert ${item.a}`, 'muted')] : kind === 'audit' ? [pill(item.sev, sevKind(item.sev)), pill(item.s, fixKind(item.s))] : [pill(item.role, 'brand'), pill(item.ready, 'good')];
    const body = kind === 'monitor' ? item.nc ? 'Description pending editorial review; the signal is live.' : item.d : kind === 'audit' ? item.why : item.d;
    return <button key={title} onClick={() => setSel({
      kind,
      item
    })} style={{
      display: 'block',
      width: '100%',
      textAlign: 'left',
      border: '1px solid rgba(128,128,128,.28)',
      borderRadius: '10px',
      padding: '12px 14px',
      marginBottom: '8px',
      background: 'transparent',
      cursor: 'pointer',
      font: 'inherit',
      color: 'inherit'
    }}>
        <div style={{
      display: 'flex',
      gap: '10px',
      justifyContent: 'space-between',
      alignItems: 'flex-start',
      flexWrap: 'wrap'
    }}>
          <strong style={{
      fontSize: '14px'
    }}>{title}</strong>
          <span style={{
      display: 'flex',
      gap: '5px',
      flexWrap: 'wrap'
    }}>{badges}</span>
        </div>
        {body ? <div style={{
      fontSize: '12.5px',
      opacity: 0.72,
      marginTop: '4px'
    }}>{body}</div> : null}
      </button>;
  };
  const tabBtn = (id, label) => <button key={id} onClick={() => {
    setTab(id);
    setLimit(8);
    setQ('');
  }} style={{
    border: 0,
    background: 'none',
    font: 'inherit',
    fontWeight: 600,
    fontSize: '14px',
    padding: '9px 14px',
    cursor: 'pointer',
    color: tab === id ? '#5529d6' : 'inherit',
    opacity: tab === id ? 1 : 0.65,
    borderBottom: tab === id ? '2px solid #5529d6' : '2px solid transparent'
  }}>
      {label}
    </button>;
  const inputStyle = {
    border: '1px solid rgba(128,128,128,.3)',
    borderRadius: '999px',
    padding: '7px 13px',
    font: 'inherit',
    fontSize: '13px',
    background: 'transparent',
    color: 'inherit'
  };
  return <div style={{
    position: 'relative'
  }}>
      {}
      <div style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fit,minmax(120px,1fr))',
    gap: '10px',
    margin: '18px 0'
  }}>
        {(d.stats || []).map(([v, l]) => <div key={l} style={{
    border: '1px solid rgba(128,128,128,.28)',
    borderRadius: '10px',
    padding: '11px 13px'
  }}>
            <div style={{
    fontSize: v.length > 6 ? '15px' : '21px',
    fontWeight: 700,
    letterSpacing: '-.02em',
    color: v.length > 6 ? '#5529d6' : 'inherit'
  }}>{v}</div>
            <div style={{
    fontSize: '11px',
    opacity: 0.66
  }}>{l}</div>
          </div>)}
      </div>

      <div style={{
    display: 'flex',
    gap: '4px',
    borderBottom: '1px solid rgba(128,128,128,.25)',
    marginBottom: '16px',
    flexWrap: 'wrap'
  }}>
        {tabBtn('overview', 'Overview')}
        {tabBtn('monitor', `Monitor (${monitor.length})`)}
        {tabBtn('audit', `Audit (${audits.length})`)}
        {tabBtn('automate', 'Automate')}
      </div>

      {tab === 'overview' && <div>
          <div style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fit,minmax(240px,1fr))',
    gap: '10px'
  }}>
            {outcomes.map(o => <div key={o} style={{
    border: '1px solid rgba(128,128,128,.28)',
    borderLeft: '3px solid #5529d6',
    borderRadius: '10px',
    padding: '11px 14px'
  }}>
                <strong style={{
    fontSize: '13.5px'
  }}>{o}</strong>
              </div>)}
          </div>
          <p style={{
    fontSize: '13px',
    opacity: 0.75,
    marginTop: '16px'
  }}>
            Every capability follows the same controlled sequence: connect, monitor, detect, recommend,
            approve, execute, verify. Nothing changes a connected system without the approval step.
          </p>
        </div>}

      {tab !== 'overview' && <div>
          <div style={{
    display: 'flex',
    gap: '8px',
    marginBottom: '12px',
    flexWrap: 'wrap'
  }}>
            <input style={{
    ...inputStyle,
    flex: 1,
    minWidth: '170px'
  }} placeholder={`Search ${tab === 'monitor' ? 'signals' : tab === 'audit' ? 'checks' : 'workflows'}...`} value={q} onChange={e => {
    setQ(e.target.value);
    setLimit(8);
  }} />
            {tab !== 'automate' && <select style={inputStyle} value={outcome} onChange={e => {
    setOutcome(e.target.value);
    setLimit(8);
  }}>
                <option value="">All outcomes</option>
                {outcomes.map(o => <option key={o} value={o}>{o}</option>)}
              </select>}
            {tab === 'audit' && <select style={inputStyle} value={status} onChange={e => {
    setStatus(e.target.value);
    setLimit(8);
  }}>
                <option value="">All fix statuses</option>
                <option>Prepared fix</option>
                <option>Candidate remediation</option>
                <option>Report only</option>
              </select>}
          </div>

          {rows.length === 0 && tab === 'automate' && <div style={{
    border: '1px dashed #5529d6',
    borderRadius: '10px',
    padding: '15px'
  }}>
              <strong style={{
    fontSize: '14px'
  }}>Ready to build your first {d.name} workflow</strong>
              <p style={{
    fontSize: '12.5px',
    opacity: 0.75,
    margin: '6px 0 0'
  }}>
                Vortex IQ is integrated with {d.reads} read and {d.writes} write operations on {d.name}.
                Pick a trigger, add them as steps, and every step that changes data pauses for approval.
              </p>
            </div>}
          {rows.length === 0 && tab !== 'automate' && <div style={{
    opacity: 0.6,
    fontSize: '13px'
  }}>Nothing matches this search or filter.</div>}

          {rows.slice(0, limit).map(r => card(r, tab))}

          {rows.length > limit && <button onClick={() => setLimit(rows.length)} style={{
    ...inputStyle,
    display: 'block',
    margin: '10px auto 0',
    cursor: 'pointer'
  }}>
              View all {rows.length}
            </button>}
        </div>}

      {}
      {sel && <>
          <div onClick={() => setSel(null)} style={{
    position: 'fixed',
    inset: 0,
    background: 'rgba(10,6,26,.45)',
    zIndex: 40
  }} />
          <aside style={{
    position: 'fixed',
    top: 0,
    right: 0,
    width: 'min(430px, 94vw)',
    height: '100vh',
    overflowY: 'auto',
    background: 'var(--background, #fff)',
    backgroundColor: 'light-dark(#fff, #0c061f)',
    borderLeft: '1px solid rgba(128,128,128,.3)',
    padding: '20px 22px',
    zIndex: 50
  }}>
            <button onClick={() => setSel(null)} style={{
    float: 'right',
    border: '1px solid rgba(128,128,128,.3)',
    background: 'transparent',
    color: 'inherit',
    borderRadius: '999px',
    width: '30px',
    height: '30px',
    cursor: 'pointer'
  }}>
              ×
            </button>
            <DetailBody sel={sel} d={d} pill={pill} sevKind={sevKind} fixKind={fixKind} />
          </aside>
        </>}
    </div>;
};

Monitor Keap (Infusionsoft) pipeline, responsiveness and customer-satisfaction signals, and catch stalls before they become churn.

No changes are made without the configured approval policy. Read-only operations do not modify the connected system; schedules, access scopes, API usage and data handling remain governed by Vortex IQ controls.

[Connect or manage this source](https://app.vortexiq.ai/workbench/settings/sources) · [How connecting works](/integrations/connector-catalogue) · [Create a workflow](https://app.vortexiq.ai/workbench/flows/create?connector=keap)

<CapabilityPage data={{"name": "Keap (Infusionsoft)", "reads": 9, "writes": 3, "stats": [["32", "performance signals"], ["2", "automated checks"], ["0", "prepared fixes"], ["0", "proven workflows"], ["12", "API operations"]], "resRows": [{"n": "campaigns", "r": 2, "w": 0}, {"n": "tagcontacts", "r": 1, "w": 1}, {"n": "account profiles", "r": 1, "w": 0}, {"n": "campaign sequencecontacts", "r": 0, "w": 1}, {"n": "contacts", "r": 1, "w": 0}, {"n": "emails", "r": 1, "w": 0}, {"n": "orders", "r": 1, "w": 0}, {"n": "tags", "r": 1, "w": 0}], "opsPhrase": "over 13,000", "connPhrase": "more than 200", "monitor": [{"n": "Bounce Rate", "o": "Protect revenue", "a": "2 / 5", "d": "Share of sends that bounced. Rising bounces hurt sender reputation - investigate list quality.", "nc": false}, {"n": "Keap Email Revenue vs Storefront Revenue", "o": "Protect revenue", "a": "Merchant rule", "d": "Reconciles Keap-attributed revenue against the storefront's own order revenue for the same customers - exposes attribution gaps and double-counting.", "nc": false}, {"n": "Spam Complaint Rate", "o": "Protect revenue", "a": "0.1 / 0.3", "d": "Spam complaints per send. Above 0.1% puts deliverability at risk.", "nc": false}, {"n": "Storefront Buyers Not in Any Keap Campaign", "o": "Protect revenue", "a": "Merchant rule", "d": "Paying storefront customers who never enter a Keap automation - missed lifecycle revenue. Enrol them.", "nc": false}, {"n": "Total Revenue", "o": "Grow revenue", "a": "Merchant rule", "d": "Order/transaction revenue attributed to Keap email + checkout over the window. The hero number.", "nc": false}, {"n": "Active Subscribers (Est.)", "o": "Run operations", "a": "Merchant rule", "d": "Estimated subscribed, reachable contacts (marketable email_status).", "nc": false}, {"n": "CRM Data Hygiene Score", "o": "Run operations", "a": "95 / 80", "d": "Composite of two confirmed real Keap Contact fields (requested via the /contacts optional_properties param): share with a populated email_addresses entry and share with a populated phone_numbers entry.", "nc": false}, {"n": "Campaign Status Breakdown", "o": "Grow revenue", "a": "Merchant rule", "d": "Email broadcasts grouped by status (sent, draft, scheduled).", "nc": false}, {"n": "Checkout-to-Order Rate", "o": "Grow revenue", "a": "Merchant rule", "d": "Share of started checkouts (draft orders) that became paid orders.", "nc": false}, {"n": "Checkouts Started", "o": "Customer experience", "a": "Merchant rule", "d": "Checkouts begun (draft orders) from email clicks, where Keap reports it.", "nc": false}, {"n": "Click-to-Open Rate", "o": "Grow revenue", "a": "10 / 3", "d": "Clicks as a share of opens - engagement quality independent of open-rate inflation.", "nc": false}, {"n": "Conversion Rate", "o": "Grow revenue", "a": "Merchant rule", "d": "Orders as a share of emails sent - ties send volume to revenue.", "nc": false}, {"n": "Delivery Rate", "o": "Protect revenue", "a": "98 / 95", "d": "Share of sends accepted by the inbox provider. Below 95% needs attention.", "nc": false}, {"n": "Email Click Rate", "o": "Grow revenue", "a": "2 / 0.5", "d": "Share of delivered emails that earned a click. The clearest engagement signal.", "nc": false}, {"n": "Email Health KPIs", "o": "Protect revenue", "a": "Merchant rule", "d": "Delivered, bounced and unsubscribed at a glance - your deliverability vitals.", "nc": false}, {"n": "Email Open Rate", "o": "Run operations", "a": "20 / 10", "d": "Share of delivered emails opened. iOS Mail Privacy inflates this - pair with click-to-open.", "nc": false}, {"n": "Engagement Funnel", "o": "Grow revenue", "a": "Merchant rule", "d": "Sent → delivered → opened → clicked, showing where engagement drops off.", "nc": false}, {"n": "Flow Count Summary", "o": "Grow revenue", "a": "Merchant rule", "d": "How many automation campaigns exist on the account.", "nc": false}, {"n": "Flow Status Breakdown", "o": "Grow revenue", "a": "Merchant rule", "d": "Automation campaigns split by status (published, draft).", "nc": false}, {"n": "Flow Trigger Types", "o": "Grow revenue", "a": "Merchant rule", "d": "Automation campaigns grouped by what triggers them (goals / sequences).", "nc": false}, {"n": "Flows List", "o": "Grow revenue", "a": "Merchant rule", "d": "Every automation campaign and its current status.", "nc": false}, {"n": "List Count", "o": "Grow revenue", "a": "Merchant rule", "d": "Number of tags / audiences on the account.", "nc": false}, {"n": "List Health Summary", "o": "Grow revenue", "a": "Merchant rule", "d": "Total reachable members across all tags / audiences.", "nc": false}, {"n": "Lists Overview", "o": "Grow revenue", "a": "Merchant rule", "d": "Every audience (tag) with its member count.", "nc": false}, {"n": "Placed Orders Total", "o": "Run operations", "a": "Merchant rule", "d": "Orders attributed to Keap in the window.", "nc": false}, {"n": "Refund Rate", "o": "Protect revenue", "a": "Merchant rule", "d": "Share of attributed orders later refunded (status REFUNDED / PARTIALLY_REFUNDED).", "nc": false}, {"n": "Revenue per Recipient", "o": "Grow revenue", "a": "Merchant rule", "d": "Revenue divided by emails sent - what each address is worth per send.", "nc": false}, {"n": "Revenue per Send", "o": "Grow revenue", "a": "Merchant rule", "d": "Revenue divided by emails sent - the efficiency of each send.", "nc": false}, {"n": "Segments Overview", "o": "Catalogue quality", "a": "Merchant rule", "d": "Saved segments (tag categories) and their sizes.", "nc": false}, {"n": "Top Lists by Name", "o": "Grow revenue", "a": "Merchant rule", "d": "Largest audiences (tags) by member count.", "nc": false}, {"n": "Total Sends", "o": "Run operations", "a": "Merchant rule", "d": "Total email broadcasts sent across the window.", "nc": false}, {"n": "Unsubscribe Rate", "o": "Protect revenue", "a": "0.5 / 2", "d": "Unsubscribes per send. A spike signals list fatigue or off-target content.", "nc": false}], "audits": [{"n": "CRM API token invalid or scope insufficient", "o": "Catalogue quality", "sev": "critical", "s": "Report only", "why": "Once this credential fails, VortexIQ stops seeing deals, contacts and pipeline activity from this CRM entirely, and every pipeline and data-quality finding here goes stale from that moment, silently, until someone notices the numbers stopped updating.", "codes": ["CRM-AUTH-001"]}, {"n": "CRM data hygiene below 80% (missing required fields)", "o": "Run operations", "sev": "medium", "s": "Report only", "why": "More than 1 in 5 records is missing a required field. Incomplete records make segmentation, reporting and any automation keyed off those fields (routing, scoring, follow-up sequences) unreliable, so the gap compounds into every process built on top of this data, not just the records themselves.", "codes": ["CRM-DATA-001"]}], "autos": [], "inValidation": 0}} />
