ConnectWise Automate API Patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ConnectWise Automate 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 Automate REST API v1 provides programmatic access to computers, clients, scripts, monitors, alerts, and more. This skill covers authentication, token management, pagination, filtering, error handling, and performance optimization patterns.
"ConnectWise" is an umbrella brand over three products with three unrelated APIs. Loading the wrong one produces auth failures that read like permission problems:
api-*.myconnectwise.net/{codebase}/apis/3.0/, authenticated with public/private key plus a clientId header, and filtered with plural conditions=. An Automate bearer token will not authenticate against it. Use connectwise-psa-api-patterns.
again; use connectwise-cpq-api-patterns.
https://{automate-server}/cwa/api/v1/Replace {automate-server} with your Automate server hostname.
| Method | Description | Use Case |
|---|---|---|
| Integrator | Server-to-server credentials | API integrations, automation |
| User + 2FA | User credentials with optional MFA | User-context operations |
┌─────────────┐ 1. POST /APICredentials ┌─────────────────────┐
│ Client │ ─────────────────────────────> │ Automate Server │
│ │ (username + password) │ │
│ │ <───────────────────────────── │ │
└─────────────┘ 2. Access Token + Expiry └─────────────────────┘
│
│ 3. API Request with Authorization Header
▼
┌───────────────────────────────────────────────────────────────────┐
│ GET /cwa/api/v1/Computers │
│ Authorization: Bearer <access_token> │
└───────────────────────────────────────────────────────────────────┘# Integrator credentials (recommended for automation)
export CONNECTWISE_AUTOMATE_SERVER="automate.example.com"
export CONNECTWISE_AUTOMATE_USERNAME="integrator-username"
export CONNECTWISE_AUTOMATE_PASSWORD="integrator-password"
# User credentials with optional 2FA
export CONNECTWISE_AUTOMATE_SERVER="automate.example.com"
export CONNECTWISE_AUTOMATE_USER="username"
export CONNECTWISE_AUTOMATE_PASS="password"
export CONNECTWISE_AUTOMATE_2FA="optional-2fa-key"interface TokenResponse {
AccessToken: string; // Bearer token for API requests
TokenType: string; // "Bearer"
ExpiresIn: number; // Seconds until expiry
RefreshToken: string; // Token for refresh (if enabled)
UserID: number; // Authenticated user ID
Username: string; // Authenticated username
}POST /cwa/api/v1/APICredentials
Content-Type: application/json
{
"Username": "{integrator-username}",
"Password": "{integrator-password}"
}Response:
{
"AccessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"TokenType": "Bearer",
"ExpiresIn": 14400,
"UserID": 1,
"Username": "integrator"
}POST /cwa/api/v1/APICredentials
Content-Type: application/json
{
"Username": "{username}",
"Password": "{password}",
"TwoFactorCode": "{6-digit-code}"
}| Header | Value | Description |
|---|---|---|
Authorization | Bearer {token} | Required for all API requests |
Content-Type | application/json | Required for POST/PUT/PATCH |
Accept | application/json | Response format |
POST /cwa/api/v1/APICredentials/Refresh
Content-Type: application/json
{
"RefreshToken": "{refresh-token}"
}ConnectWise Automate uses offset-based pagination with lowercase page/pageSize query parameters (max pageSize is 1000):
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page | integer | 1 | - | Page number (1-based) |
pageSize | integer | 50 | 1000 | Items per page |
GET /cwa/api/v1/Computers?page=1&pageSize=100
Authorization: Bearer {token}Track pagination via response headers rather than assuming page count:
| Header | Description |
|---|---|
X-Total-Count | Total number of items |
X-Page | Current page number |
X-Page-Size | Items per page |
X-Total-Pages | Total number of pages |
See references/examples.md for a complete fetchAllComputers pagination loop that reads X-Total-Pages and paces requests to respect rate limits.
ConnectWise Automate supports OData-style filtering with the condition parameter.
| Operator | Description | Example |
|---|---|---|
= | Equal | Status = 'Online' |
!= | Not equal | Status != 'Offline' |
> | Greater than | ComputerID > 100 |
< | Less than | TotalMemory < 4096 |
>= | Greater or equal | Severity >= 3 |
<= | Less or equal | DiskFreePercent <= 10 |
contains | String contains | Name contains 'DC' |
startswith | String starts with | Name startswith 'ACME' |
endswith | String ends with | Name endswith '01' |
in | Value in list | Status in ('Online','Offline') |
| Operator | Description | Example |
|---|---|---|
and | Logical AND | Status = 'Online' and ClientID = 100 |
or | Logical OR | Status = 'Offline' or Status = 'Unknown' |
not | Logical NOT | not (Status = 'Offline') |
# Computers that are online
GET /cwa/api/v1/Computers?condition=Status = 'Online'
# Computers for a specific client
GET /cwa/api/v1/Computers?condition=ClientID = 100
# Windows servers that are online
GET /cwa/api/v1/Computers?condition=OS contains 'Server' and Status = 'Online'
# Computers with names starting with "ACME"
GET /cwa/api/v1/Computers?condition=Name startswith 'ACME'
# Alerts with severity 3 or higher
GET /cwa/api/v1/Alerts?condition=Severity >= 3
# Active alerts for a client
GET /cwa/api/v1/Alerts?condition=ClientID = 100 and Status in ('New','Active')
# Offline computers with recent contact
GET /cwa/api/v1/Computers?condition=Status = 'Offline' and LastContact >= '2024-02-14'Always URL-encode the condition parameter:
const condition = "Status = 'Online' and ClientID = 100";
const url = `/Computers?condition=${encodeURIComponent(condition)}`;ConnectWise Automate enforces rate limits to protect server resources (~60 requests per minute, ~10 concurrent; daily limits vary by config). Exceeding them returns HTTP 429.
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per window |
X-RateLimit-Remaining | Remaining requests |
X-RateLimit-Reset | Seconds until reset |
Retry-After | Seconds to wait (on 429) |
On a 429, read Retry-After and wait before retrying; on a 5xx, retry with exponential backoff. See references/examples.md for a requestWithRetry implementation.
Common statuses: 401 (token expired - re-authenticate), 403 (permission denied), 404 (not found), 429 (rate limited - see Rate Limiting above), 500/503 (retry with backoff). Error responses are shaped as { "error": { "code", "message", "details" } }.
See references/errors.md for the complete HTTP status code table, error response format, and a reusable AutomateAPIError/handleApiResponse error-handling pattern.
A full ConnectWiseAutomateClient class that combines token caching, retry-with-backoff, and pagination-aware request methods (getComputers, getClients, getAlerts, runScript) is available in references/examples.md.
condition parameter, not client-side filteringconst computers = await client.getComputers(
"ClientID = 100 and Status = 'Online'"
);const alerts = await client.getAlerts(
"Severity >= 3 and Status in ('New','Active')"
);const computers = await client.getComputers(
"Name contains 'DC'"
);const yesterday = new Date(Date.now() - 86400000).toISOString();
const computers = await client.getComputers(
`Status = 'Offline' and LastContact >= '${yesterday}'`
);async function batchProcess(items, processor, { batchSize = 10, delayMs = 1000 }) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
// Process batch in parallel
const batchResults = await Promise.all(
batch.map(item => processor(item).catch(e => ({ error: e.message })))
);
results.push(...batchResults);
// Respect rate limits between batches
if (i + batchSize < items.length) {
await sleep(delayMs);
}
}
return results;
}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.