Files
zeus-skills/ops/connectwise/connectwise-reporting/references/customer-aggregation-recipe.md
T

2.7 KiB

ConnectWise Customer Aggregation Recipe

Reference: How to aggregate open tickets by customer and enrich with primary contact details.

Use Case

  • "List customers who have had open tickets in the past N years"
  • "Show me every company with active Help Desk tickets and their primary contact email"

API Traversal

import base64
import requests
from datetime import datetime, timedelta

# Auth
creds = {...}  # from ~/.secrets/connectwise
auth_str = f"{company_id}{public_key}:{private_key}"
encoded_auth = base64.b64encode(auth_str.encode()).decode()
headers = {
    'Authorization': f'Basic {encoded_auth}',
    'clientId': client_id,
    'Company-Id': company_id,
    'Content-Type': 'application/json'
}

# 1. Fetch tickets
base_url = 'https://cw.vistanetinc.com/v4_6_release/apis/3.0'
two_years_ago = (datetime.now() - timedelta(days=730)).strftime('%Y-%m-%dT%H:%M:%SZ')
conditions = f"board/id=1 and closedFlag=false and dateEntered>'{two_years_ago}'"
url = f"{base_url}/service/tickets?conditions={conditions}&pageSize=1000"
tickets = requests.get(url, headers=headers).json()

# 2. Aggregate by company
companies = {}
for t in tickets:
    status_name = t.get('status', {}).get('name', '')
    if status_name.startswith('>'):
        continue
    comp_id = t.get('company', {}).get('id')
    if not comp_id:
        continue
    companies.setdefault(comp_id, {
        'name': t['company']['name'],
        'ticket_count': 0,
        'latest_date': t['_info']['dateEntered']
    })
    companies[comp_id]['ticket_count'] += 1
    if t['_info']['dateEntered'] > companies[comp_id]['latest_date']:
        companies[comp_id]['latest_date'] = t['_info']['dateEntered']

# 3. Enrich with primary contact email
for comp_id, data in companies.items():
    comp = requests.get(f"{base_url}/company/companies/{comp_id}", headers=headers).json()
    contact_id = comp.get('defaultContact', {}).get('id')
    data['contact_name'] = comp.get('defaultContact', {}).get('name', 'N/A')
    data['email'] = comp.get('invoiceToEmailAddress', 'N/A')

    if contact_id:
        contact = requests.get(f"{base_url}/company/contacts/{contact_id}", headers=headers).json()
        for item in contact.get('communicationItems', []):
            if item.get('type', {}).get('name') == 'Email' and item.get('defaultFlag'):
                data['email'] = item['value']
                break

Board IDs

  • VistaNet Help Desk = 1
  • VistaNet Management = 25
  • Managed Service Alerts = 6

Known Gaps

  • Some companies (e.g., BJ LaRossa, Far West Rice Inc.) have no default contact email in CW. Display N/A gracefully.
  • invoiceToEmailAddress on the company record is often billing/AP, not the operational contact.