ConnectWise Manage API Patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ConnectWise Manage API Patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The ConnectWise PSA REST API provides access to all PSA entities including tickets, companies, contacts, projects, and time entries. This skill covers authentication, query syntax, pagination, rate limiting, and best practices for API integration.
"ConnectWise" is an umbrella brand over three products with three unrelated APIs. Loading the wrong one produces auth failures that read like permission problems:
/cwa/api/v1/ basepath, Bearer-token auth, and singular condition= filters. PSA public/private keys will not authenticate against it. Use connectwise-automate-api-patterns.
again; use connectwise-cpq-api-patterns.
| Region | Base URL |
|---|---|
| North America | https://api-na.myconnectwise.net/{codebase}/apis/3.0/ |
| Europe | https://api-eu.myconnectwise.net/{codebase}/apis/3.0/ |
| Australia | https://api-au.myconnectwise.net/{codebase}/apis/3.0/ |
Replace {codebase} with your company identifier (e.g., v4_6_release or custom).
Some instances may use legacy URLs:
https://api-na.myconnectwise.net/v4_6_release/apis/3.0/
https://api-staging.connectwisedev.com/v4_6_release/apis/3.0/ConnectWise PSA uses Basic Authentication with a combined credential string plus a Client ID header.
Authorization: Basic base64({companyId}+{publicKey}:{privateKey})
clientId: {your-client-id} companyId + "+" + publicKey + ":" + privateKey
Example: company+publickey:privatekey base64("company+publickey:privatekey") = "Y29tcGFueStwdWJsaWNrZXk6cHJpdmF0ZWtleQ==" Authorization: Basic Y29tcGFueStwdWJsaWNrZXk6cHJpdmF0ZWtleQ==
clientId: your-registered-client-id
Content-Type: application/jsonGET /v4_6_release/apis/3.0/service/tickets
Host: api-na.myconnectwise.net
Authorization: Basic Y29tcGFueStwdWJsaWNrZXk6cHJpdmF0ZWtleQ==
clientId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Content-Type: application/jsonSee references/examples.md for a JavaScript authentication example and recommended environment variable setup.
conditions=field operator value| Operator | Description | Example |
|---|---|---|
= | Equals | status/id=1 |
!= | Not equals | status/id!=5 |
< | Less than | priority/id<3 |
<= | Less than or equal | priority/id<=2 |
> | Greater than | dateEntered>2024-01-01 |
>= | Greater than or equal | dateEntered>=2024-01-01 |
contains | Contains substring | summary contains "email" |
like | Pattern match | summary like "%email%" |
in | In list | status/id in (1,2,3) |
not in | Not in list | status/id not in (5) |
Use / to reference nested fields:
company/id=12345
status/name="New"
contact/firstName contains "John"AND (default):
conditions=company/id=12345 and status/id!=5 and priority/id<=2OR:
conditions=status/id=1 or status/id=2Complex:
conditions=(status/id=1 or status/id=2) and company/id=12345Date format: YYYY-MM-DD or ISO 8601
conditions=dateEntered>=[2024-01-01]
conditions=dateEntered>=[2024-01-01T00:00:00Z] and dateEntered<[2024-02-01T00:00:00Z]Exact match:
conditions=summary="Email not working"Contains:
conditions=summary contains "email"Like (wildcards):
conditions=summary like "%email%"
conditions=company/identifier like "AC%"conditions=contact=null
conditions=assignedResource!=nullSpecial characters must be URL-encoded:
| Character | Encoded |
|---|---|
| Space | %20 |
= | %3D |
< | %3C |
> | %3E |
" | %22 |
Example:
GET /service/tickets?conditions=company/id%3D12345%20and%20status/id!%3D5| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page | int | 1 | - | Page number (1-based) |
pageSize | int | 25 | 1000 | Records per page |
GET /service/tickets?page=1&pageSize=100| Header | Description |
|---|---|
Link | Contains next/prev page URLs |
X-Total-Count | Total record count (if requested) |
Paginate by incrementing page until the response has fewer records than pageSize. See references/examples.md for a full fetch-all-pages implementation.
GET /service/tickets?conditions=status/id!=5&pageSize=1&fields=idCheck X-Total-Count header or use /count endpoint:
GET /service/tickets/count?conditions=status/id!=5| Limit | Value |
|---|---|
| Requests per minute | 60 |
| Per API member | Yes |
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute |
X-RateLimit-Remaining | Requests remaining in window |
X-RateLimit-Reset | Seconds until limit resets |
When rate limited, you receive HTTP 429:
{
"code": "RateLimitExceeded",
"message": "Rate limit exceeded. Try again in 30 seconds."
}Implement exponential backoff with jitter on 429s using the Retry-After header. See references/examples.md for a retry strategy implementation.
Errors return the relevant HTTP status code plus a JSON body with code and message fields, and per-field detail in errors[]. See references/errors.md for the complete HTTP status code table, error response format, and common error codes.
Request specific fields only:
GET /service/tickets?fields=id,summary,status/name,company/nameGET /service/tickets?orderBy=priority/id asc, dateEntered descInclude child records:
GET /service/tickets?childconditions=notes/text contains "update"GET /service/tickets?customFieldConditions=customField1 contains "value"ConnectWise can POST entity-change events to a registered callback URL. See references/webhooks.md for the callback payload shape and the registration request.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.