ConnectWise PSA API Patterns
Overview
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.
Anti-triggers
"ConnectWise" is an umbrella brand over three products with three unrelated APIs. Loading the wrong one produces auth failures that read like permission problems:
- ConnectWise Automate — on-premise RMM server,
/cwa/api/v1/base path, Bearer-token auth, and singularcondition=filters. PSA public/private keys will not authenticate against it. Useconnectwise-automate-api-patterns. - ConnectWise CPQ (Sell/Quosal) — its own host and credential set
again; use
connectwise-cpq-api-patterns.
Base URLs
| 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).
Legacy URLs
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/
Authentication
Public/Private Key + Client ID
ConnectWise PSA uses Basic Authentication with a combined credential string plus a Client ID header.
Credential Format
Authorization: Basic base64({companyId}+{publicKey}:{privateKey})
clientId: {your-client-id}
Step-by-Step Authentication
Combine credentials:
companyId + "+" + publicKey + ":" + privateKey Example: company+publickey:privatekeyBase64 encode:
base64("company+publickey:privatekey") = "Y29tcGFueStwdWJsaWNrZXk6cHJpdmF0ZWtleQ=="Set headers:
Authorization: Basic Y29tcGFueStwdWJsaWNrZXk6cHJpdmF0ZWtleQ== clientId: your-registered-client-id Content-Type: application/json
Example Request
GET /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/json
See references/examples.md for a JavaScript authentication example and recommended environment variable setup.
Obtaining Credentials
- API Member: Create in System > Members > API Members
- Public/Private Keys: Generate for API member
- Client ID: Register at ConnectWise Developer Portal
Conditions Query Syntax
Basic Syntax
conditions=field operator value
Supported Operators
| 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) |
Field References
Use / to reference nested fields:
company/id=12345
status/name="New"
contact/firstName contains "John"
Combining Conditions
AND (default):
conditions=company/id=12345 and status/id!=5 and priority/id<=2
OR:
conditions=status/id=1 or status/id=2
Complex:
conditions=(status/id=1 or status/id=2) and company/id=12345
Date Conditions
Date 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]
String Conditions
Exact match:
conditions=summary="Email not working"
Contains:
conditions=summary contains "email"
Like (wildcards):
conditions=summary like "%email%"
conditions=company/identifier like "AC%"
Null Checks
conditions=contact=null
conditions=assignedResource!=null
URL Encoding
Special 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
Pagination
Request Parameters
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page |
int | 1 | - | Page number (1-based) |
pageSize |
int | 25 | 1000 | Records per page |
Example Request
GET /service/tickets?page=1&pageSize=100
Response Headers
| 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.
Getting Total Count
GET /service/tickets?conditions=status/id!=5&pageSize=1&fields=id
Check X-Total-Count header or use /count endpoint:
GET /service/tickets/count?conditions=status/id!=5
Rate Limiting
Limits
| Limit | Value |
|---|---|
| Requests per minute | 60 |
| Per API member | Yes |
Rate Limit Headers
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per minute |
X-RateLimit-Remaining |
Requests remaining in window |
X-RateLimit-Reset |
Seconds until limit resets |
429 Response
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.
Best Practices for Rate Limits
- Implement exponential backoff - Don't hammer the API
- Check headers - Monitor remaining requests
- Batch operations - Reduce total requests
- Use webhooks - Instead of polling for changes
Error Handling
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.
Common API Patterns
Field Selection
Request specific fields only:
GET /service/tickets?fields=id,summary,status/name,company/name
Ordering
GET /service/tickets?orderBy=priority/id asc, dateEntered desc
Child Collections
Include child records:
GET /service/tickets?childconditions=notes/text contains "update"
Custom Fields
GET /service/tickets?customFieldConditions=customField1 contains "value"
Webhook Configuration
ConnectWise can POST entity-change events to a registered callback URL. See references/webhooks.md for the callback payload shape and the registration request.
Best Practices
- Store credentials securely - Never commit to source control
- Handle errors gracefully - Retry transient failures
- Use pagination - Don't fetch unbounded results
- Select needed fields - Reduce payload size
- Log API calls - For debugging and audit
- Monitor usage - Track API call patterns
API Documentation
Related Skills
- ConnectWise Tickets - Ticket management
- ConnectWise Companies - Company management
- ConnectWise Contacts - Contact management
- ConnectWise Projects - Project management
- ConnectWise Time Entries - Time tracking