Summary and recommendation
The ConnectWise Manage REST API exposes full CRUD on Member records under the `/v4_6_release/apis/3.0/system/members` resource. Authentication uses HTTP Basic Auth with a compound credential - `{companyId}+{publicKey}:{privateKey}` Base64-encoded - plus a mandatory `clientId` header registered at developer.connectwise.com; requests missing the `clientId` return 401 regardless of credential validity.
The base URL is instance-specific: cloud tenants use a ConnectWise-assigned subdomain, while on-premise deployments use the customer's own domain, so the host must be resolved dynamically in any identity graph that maps ConnectWise members to canonical user records across systems.
There is no native SCIM 2.0 endpoint; automated provisioning requires direct REST API calls or a middleware layer such as Okta Workflows or a custom integration pipeline.
API quick reference
| Has user API | Yes |
| Auth method | HTTP Basic Auth using a compound credential: 'companyId+publicKey:privateKey' Base64-encoded in the Authorization header. API keys are generated per Member in ConnectWise Manage. |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: HTTP Basic Auth using a compound credential: 'companyId+publicKey:privateKey' Base64-encoded in the Authorization header. API keys are generated per Member in ConnectWise Manage.
Setup steps
- Log in to ConnectWise Manage as an admin.
- Navigate to System > Members, open the target member record.
- Go to the API Keys tab and click '+' to generate a new Public/Private key pair.
- Construct the username as: {companyId}+{publicKey} (e.g., mycompany+abc123).
- Base64-encode the string '{companyId}+{publicKey}:{privateKey}'.
- Set the Authorization header: 'Authorization: Basic {base64string}'.
- Also set header 'clientId: {your-registered-clientId}' - register your clientId at developer.connectwise.com.
- Set 'Content-Type: application/json' on all requests.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| Member (read) | Read access to Member records (internal users/technicians) | List and retrieve user accounts |
| Member (write) | Create, update, and delete Member records | Provisioning and deprovisioning users |
| System (read) | Read access to system-level configuration including security roles | Reading security roles and role assignments |
| System (write) | Write access to system-level configuration | Assigning security roles to members |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique internal identifier for the Member | auto-generated | read-only | Used as path parameter in endpoint URLs |
| identifier | string | Login username for the member (unique) | required | optional | Max 15 characters; used as login credential |
| firstName | string | Member's first name | required | optional | |
| lastName | string | Member's last name | required | optional | |
| emailAddress | string | Primary email address | required | optional | Must be unique across members |
| licenseClass | string | License type assigned to the member (e.g., 'F' = Full, 'A' = API-only) | required | optional | Determines billing and access level |
| role | object (reference) | Security role assigned to the member | required | optional | References /system/securityRoles by id |
| systemFlag | boolean | Indicates if the member is a system-level admin | optional | optional | |
| inactiveFlag | boolean | Whether the member account is inactive/disabled | optional | optional | Set to true to deactivate a user without deleting |
| defaultDepartmentId | integer | ID of the member's default department | optional | optional | |
| defaultLocationId | integer | ID of the member's default location/site | optional | optional | |
| title | string | Job title of the member | optional | optional | |
| officeEmail | string | Office email address (secondary) | optional | optional | |
| mobileEmail | string | Mobile email address | optional | optional | |
| phone | string | Primary phone number | optional | optional | |
| timeZone | object (reference) | Member's time zone setting | optional | optional | |
| hireDate | string (date) | Date the member was hired (ISO 8601) | optional | optional | |
| type | object (reference) | Member type classification | optional | optional | References /system/memberTypes |
Core endpoints
List Members
- Method: GET
- URL:
https://{site}/v4_6_release/apis/3.0/system/members - Watch out for: Use the 'conditions' query parameter for server-side filtering (e.g., conditions=inactiveFlag=false). Without filtering, large member lists require many paginated requests.
Request example
GET /system/members?page=1&pageSize=50&conditions=inactiveFlag=false
Authorization: Basic {base64}
clientId: {clientId}
Response example
[
{
"id": 12,
"identifier": "jsmith",
"firstName": "John",
"lastName": "Smith",
"emailAddress": "jsmith@example.com",
"licenseClass": "F",
"inactiveFlag": false
}
]
Get Member by ID
- Method: GET
- URL:
https://{site}/v4_6_release/apis/3.0/system/members/{id} - Watch out for: Returns 404 if the member ID does not exist or the API key lacks read permission on Members.
Request example
GET /system/members/12
Authorization: Basic {base64}
clientId: {clientId}
Response example
{
"id": 12,
"identifier": "jsmith",
"firstName": "John",
"lastName": "Smith",
"emailAddress": "jsmith@example.com",
"licenseClass": "F",
"role": {"id": 3, "name": "Admin"}
}
Create Member
- Method: POST
- URL:
https://{site}/v4_6_release/apis/3.0/system/members - Watch out for: identifier must be unique and ≤15 characters. licenseClass and role are required. Creating a member consumes a license seat immediately.
Request example
POST /system/members
Content-Type: application/json
{
"identifier": "bjones",
"firstName": "Bob",
"lastName": "Jones",
"emailAddress": "bjones@example.com",
"licenseClass": "F",
"role": {"id": 3}
}
Response example
{
"id": 45,
"identifier": "bjones",
"firstName": "Bob",
"lastName": "Jones",
"emailAddress": "bjones@example.com",
"licenseClass": "F"
}
Update Member (partial)
- Method: PATCH
- URL:
https://{site}/v4_6_release/apis/3.0/system/members/{id} - Watch out for: PATCH uses JSON Patch format (RFC 6902 array of operations). PUT replaces the full object and requires all required fields.
Request example
PATCH /system/members/45
Content-Type: application/json
[
{"op": "replace", "path": "/emailAddress", "value": "bob.jones@example.com"},
{"op": "replace", "path": "/inactiveFlag", "value": false}
]
Response example
{
"id": 45,
"identifier": "bjones",
"emailAddress": "bob.jones@example.com",
"inactiveFlag": false
}
Deactivate Member
- Method: PATCH
- URL:
https://{site}/v4_6_release/apis/3.0/system/members/{id} - Watch out for: Preferred over DELETE for offboarding. Inactive members retain historical data (time entries, tickets). DELETE is permanent and may fail if the member has associated records.
Request example
PATCH /system/members/45
Content-Type: application/json
[
{"op": "replace", "path": "/inactiveFlag", "value": true}
]
Response example
{
"id": 45,
"identifier": "bjones",
"inactiveFlag": true
}
Delete Member
- Method: DELETE
- URL:
https://{site}/v4_6_release/apis/3.0/system/members/{id} - Watch out for: Will return 409 Conflict if the member has associated records (tickets, time entries, etc.). Use inactiveFlag=true instead for members with history.
Request example
DELETE /system/members/45
Authorization: Basic {base64}
clientId: {clientId}
Response example
HTTP 204 No Content
List Security Roles
- Method: GET
- URL:
https://{site}/v4_6_release/apis/3.0/system/securityRoles - Watch out for: Role IDs are required when creating or updating members. Retrieve this list first to map role names to IDs.
Request example
GET /system/securityRoles?page=1&pageSize=50
Authorization: Basic {base64}
clientId: {clientId}
Response example
[
{"id": 1, "name": "Admin"},
{"id": 2, "name": "Technician"},
{"id": 3, "name": "Sales"}
]
Get Member by Identifier (username lookup)
- Method: GET
- URL:
https://{site}/v4_6_release/apis/3.0/system/members - Watch out for: There is no direct lookup-by-username endpoint; use the conditions filter on the list endpoint. String values in conditions must be quoted with double quotes.
Request example
GET /system/members?conditions=identifier="bjones"
Authorization: Basic {base64}
clientId: {clientId}
Response example
[
{
"id": 45,
"identifier": "bjones",
"firstName": "Bob",
"lastName": "Jones"
}
]
Rate limits, pagination, and events
- Rate limits: ConnectWise Manage enforces rate limits per API client. Exact published limits are not publicly documented in detail; limits are enforced at the server level and vary by hosting environment (cloud vs. on-premise).
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: ConnectWise does not publish explicit rate limit numbers in official docs. HTTP 429 responses may be returned when limits are exceeded. Implement exponential backoff. On-premise deployments may have different limits controlled by the hosting environment.
- Pagination method: offset
- Default page size: 25
- Max page size: 1000
- Pagination pointer: page and pageSize query parameters (e.g., ?page=1&pageSize=100)
| Plan | Limit | Concurrent |
|---|---|---|
| Cloud (ConnectWise-hosted) | Not publicly specified; throttling applied server-side | 0 |
- Webhooks available: Yes
- Webhook notes: ConnectWise Manage supports callbacks (webhooks) via the 'Callbacks' feature in the API. Callbacks can be registered to fire on record-level events for various entities including Members.
- Alternative event strategy: If webhooks are insufficient, poll GET /system/members with a lastUpdated conditions filter using ISO 8601 timestamps to detect changes.
- Webhook events: member.added, member.updated, member.deleted
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- No native SCIM 2.0 endpoint is available in ConnectWise Manage as of 2025.
- SCIM is an open feature request in the ConnectWise community portal.
- SAML SSO is available via Okta and Azure AD (Entra ID) but does not include SCIM provisioning.
- Automated provisioning requires use of the REST API directly or a middleware integration platform (e.g., Okta Workflows, BetterCloud, custom scripts).
Common scenarios
Three core automation scenarios are well-supported by the API. For provisioning, call GET /system/securityRoles first to resolve role IDs, verify username availability with a conditions=identifier="{username}" filter, then POST /system/members with licenseClass, `role.
id, and required profile fields - note that identifieris capped at 15 characters and a license seat is consumed immediately on POST. For deprovisioning, preferPATCH /system/members/{id}with a JSON Patch (RFC 6902) operation settinginactiveFlag=trueoverDELETE`.
hard deletion returns 409 Conflict if the member has associated tickets or time entries, which is true of nearly any active technician record.
For directory sync, paginate GET /system/members with pageSize up to 1000 and use a lastUpdated>[ISO8601 timestamp] conditions filter for incremental runs - the conditions syntax is ConnectWise-proprietary, case-sensitive, and requires string values wrapped in double quotes.
Provision a new technician
- GET /system/securityRoles to retrieve available role IDs and identify the correct role for the new technician.
- GET /system/members?conditions=identifier="{username}" to verify the username is not already taken.
- POST /system/members with identifier, firstName, lastName, emailAddress, licenseClass='F', and role.id.
- Store the returned member id for future updates.
- Notify the user to set their password via the ConnectWise Manage UI or configure SSO so the user authenticates via the IdP.
Watch out for: identifier is capped at 15 characters. Plan a username convention that fits within this limit. License seats are consumed immediately on POST.
Deprovision a departing employee
- GET /system/members?conditions=identifier="{username}" to retrieve the member's id.
- PATCH /system/members/{id} with JSON Patch operation: [{"op":"replace","path":"/inactiveFlag","value":true}].
- Verify the response shows inactiveFlag=true.
- If the member has no associated records and a hard delete is required, attempt DELETE /system/members/{id} and handle 409 Conflict gracefully.
Watch out for: Deactivation (inactiveFlag=true) is strongly preferred over deletion. Deleted members cause referential integrity issues in historical ticket and time-entry data.
Sync user list to an external directory
- GET /system/members?page=1&pageSize=1000&conditions=inactiveFlag=false to retrieve all active members.
- Paginate through results incrementing the page parameter until the response array is empty or fewer than pageSize records are returned.
- Map ConnectWise member fields (identifier, emailAddress, firstName, lastName, role) to the external directory schema.
- For incremental sync, store the last sync timestamp and filter with conditions=lastUpdated>[{ISO8601 timestamp}] on subsequent runs.
- Handle rate limiting with exponential backoff on HTTP 429 responses.
Watch out for: The lastUpdated field filter syntax requires ISO 8601 format with timezone. The conditions filter is case-sensitive and uses ConnectWise's proprietary query syntax, not standard query languages.
Why building this yourself is a trap
Several non-obvious constraints create integration failure modes worth flagging explicitly. PATCH uses JSON Patch array format (RFC 6902), not a partial JSON body - sending a plain object to the PATCH endpoint will fail or return a 400.
Rate limits are not publicly documented and no Retry-After header is returned on 429 responses, so exponential backoff must be implemented defensively without a reliable signal.
Password management is entirely outside the REST API surface; initial credentials must be set through the UI or delegated to SSO, meaning a fully automated zero-touch provisioning flow is not achievable without SSO pre-configuration.
Finally, the identifier field is immutable after creation, which means any identity graph reconciliation logic must treat the ConnectWise identifier as a stable but potentially stale key - username changes in the upstream directory will not propagate and will require a new member record.
Automate ConnectWise workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.