Files

69 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ConnectWise Ticket Fetching Implementation
Implementation details for fetching tickets from the VistaNet ConnectWise instance.
## API Configuration
- **Base URL**: `https://cw.vistanetinc.com/v4_6_release/apis/3.0`
- **Authentication**: Basic Auth using `companyid+publickey:privatekey`.
- **Required Header**: `clientId` (Crucial: 401 Unauthorized is returned if this is missing or incorrect).
## Implementation Pattern
Since server-side filtering is unreliable, the reliable pattern is:
1. Fetch open tickets using `conditions=id >= [RecentID] AND closedFlag=false` with a large `pageSize`.
2. Filter JSON **locally in Python** for the target company by `company.id`.
3. (Optional) Filter for specific owner, board, or other criteria locally.
4. Filter out statuses starting with `>` (e.g., `>Completed`) to identify truly open tickets.
### Why local filtering is required
The `companyId` query parameter on `/service/tickets` is **silently ignored** by the API and returns tickets from all companies. Server-side `conditions` on identifiers also often return empty lists or incorrect results. Always fetch broad and filter client-side.
## Example: All open tickets for a specific company
```python
import base64, requests, json
BASE_URL = 'https://cw.vistanetinc.com/v4_6_release/apis/3.0'
CLIENT_ID = '<CLIENT_ID>'
COMPANY_ID = 'VNI'
PUBLIC_KEY = '<PUBLIC_KEY>'
PRIVATE_KEY = '<PRIVATE_KEY>'
auth_b64 = base64.b64encode(f'{COMPANY_ID}+{PUBLIC_KEY}:{PRIVATE_KEY}'.encode()).decode()
headers = {
'Authorization': f'Basic {auth_b64}',
'clientId': CLIENT_ID,
'Content-Type': 'application/json'
}
# 1. Fetch broad — companyId is ignored, so we use id range to avoid legacy mirroring
url = f'{BASE_URL}/service/tickets?pageSize=1000&conditions=id%20%3E%3D%2080000%20AND%20closedFlag%3Dfalse'
resp = requests.get(url, headers=headers, timeout=45)
resp.raise_for_status()
tickets = resp.json()
# 2. Filter client-side for Miller Glass (company.id == 19502)
miller_tickets = [
t for t in tickets
if t.get('company', {}).get('id') == 19502
and not t.get('status', {}).get('name', '').startswith('>')
and t.get('dateResolved') is None
]
```
## "Open" ticket definition
A ticket is considered open when ALL of the following are true:
- `closedFlag` is `false`
- Status name does NOT start with `>` (e.g., `>Closed`, `>Completed`)
- `dateResolved` is `None` or absent
## Searching ticket NOTES for text across a company's tickets
Note text is NOT searchable server-side on this instance: `POST /service/tickets/search` accepts `childconditions` in its FilterValues body but **silently ignores it** — verified that six different `childconditions` values (`notes/text contains '...'`) all returned identical result sets, with no error to warn you. Never conclude a note doesn't exist based on a childconditions query.
Working pattern (validated end-to-end, ~0.25 s/ticket):
1. Resolve the company: `GET /company/companies?conditions=name contains 'Partial'` → cache `company.id`.
2. Size the corpus: `GET /service/tickets/count?conditions=company/id={id}` (the count endpoint honors the condition, unlike the list endpoint's bare `companyId` param). Bound it with `AND dateEntered >= [YYYY-01-01T00:00:00Z]` when the ask is recency-scoped — the date condition is also honored and shrank a 4,301-ticket corpus to 1,086.
3. Page-fetch ticket ids: `GET /service/tickets?conditions=company/id={id} AND dateEntered >= [...]&pageSize=200&page={n}&orderBy=id asc&fields=id,summary,status/name,closedFlag,dateResolved` until a short page. Persist the array to a scratch JSON file between calls — kernel state does not survive a timeout.
4. Per ticket: `GET /service/tickets/{id}/notes?pageSize=1000` and regex the `text` client-side. pageSize=1000 on a single ticket's notes is safe; the corruption concern applies to large LIST fetches only.
5. Triage matches by snippet before reporting: generic keywords ('office', 'field') drown in "out of the office" autoresponder noise from email-logged notes — the signal is usually in `internalFlag=true` notes written by members. Email/contact notes carry `member: null`; read `contact` or the text for attribution.
Budget runtime: 1,000+ tickets of note fetches takes ~4–5 minutes — chunk the scan (~300 tickets per execute_code call) and append matches to a scratch JSON so a timeout loses nothing.