Files
zeus-skills/ops/connectwise/connectwise-reporting/references/raw-api-authentication.md
T

2.6 KiB

ConnectWise Raw API Authentication & Time Tracking

Correct Auth Construction

The credential file at ~/.secrets/connectwise is a multi-line .env file, NOT a raw string.

CW_COMPANY_ID=VNI
CW_PUBLIC_KEY=+<PUBLIC_KEY>
CW_PRIVATE_KEY=<PRIVATE_KEY>

WRONG: Base64-encoding the entire file contents as the Authorization header. RIGHT: Parse the key=value pairs first.

import base64
import os

with open(os.path.expanduser("~/.secrets/connectwise"), "r") as f:
    keys = {}
    for line in f.read().strip().splitlines():
        if '=' in line and not line.startswith('#'):
            k, v = line.split('=', 1)
            keys[k.strip()] = v.strip()

company_id = keys['CW_COMPANY_ID']
public_key = keys['CW_PUBLIC_KEY']
private_key = keys['CW_PRIVATE_KEY']

# CRITICAL: public_key already starts with '+', so do NOT add another
auth_string = f"{company_id}+{public_key}:{private_key}"
if '++' in auth_string:
    auth_string = auth_string.replace('++', '+')

encoded_auth = base64.b64encode(auth_string.encode()).decode()

headers = {
    "Authorization": f"Basic {encoded_auth}",
    "clientId": "<CLIENT_ID>",
    "Content-Type": "application/json"
}

Time Entries (Not /service/time)

Wrong Right
/service/time /time/entries
hours field actualHours field
dateEntered>'...' (quotes) dateEntered>[...] (square brackets)

Working Example

from datetime import datetime, timedelta

twelve_months_ago = (datetime.utcnow() - timedelta(days=365)).strftime('%Y-%m-%dT%H:%M:%SZ')

url = (
    "https://cw.vistanetinc.com/v4_6_release/apis/3.0"
    f"/time/entries?conditions=company/id={company_id} and dateEntered>[{twelve_months_ago}]"
    "&pageSize=1000"
)

resp = requests.get(url, headers=headers, timeout=30)
entries = resp.json()
total_hours = sum(e.get('actualHours', 0) or 0 for e in entries)

Base URL

  • VistaNet internal: https://cw.vistanetinc.com/v4_6_release/apis/3.0
  • Public endpoints (api-na.connectwise.com) are not reachable from this environment.

Known Company IDs

Company ID
<Client A> <companyId>
<Client B> <companyId>

Summary of Errors This Fixes

Symptom Root Cause Fix
500 Length cannot be less than zero Auth header was garbage (base64 of entire .env file) Parse key=value pairs
403 Forbidden on /service/time Wrong endpoint Use /time/entries
None or 0 for hours Wrong field name Use actualHours
400 ApiFindCondition for datetime Quoted string instead of bracket syntax Use dateEntered>[...]