Stitchflow
ConnectWise logo

ConnectWise User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 9, 2026

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 APIYes
Auth methodHTTP 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 URLOfficial docs
SCIM availableNo
SCIM plan requiredEnterprise

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

  1. Log in to ConnectWise Manage as an admin.
  2. Navigate to System > Members, open the target member record.
  3. Go to the API Keys tab and click '+' to generate a new Public/Private key pair.
  4. Construct the username as: {companyId}+{publicKey} (e.g., mycompany+abc123).
  5. Base64-encode the string '{companyId}+{publicKey}:{privateKey}'.
  6. Set the Authorization header: 'Authorization: Basic {base64string}'.
  7. Also set header 'clientId: {your-registered-clientId}' - register your clientId at developer.connectwise.com.
  8. 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

  1. GET /system/securityRoles to retrieve available role IDs and identify the correct role for the new technician.
  2. GET /system/members?conditions=identifier="{username}" to verify the username is not already taken.
  3. POST /system/members with identifier, firstName, lastName, emailAddress, licenseClass='F', and role.id.
  4. Store the returned member id for future updates.
  5. 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

  1. GET /system/members?conditions=identifier="{username}" to retrieve the member's id.
  2. PATCH /system/members/{id} with JSON Patch operation: [{"op":"replace","path":"/inactiveFlag","value":true}].
  3. Verify the response shows inactiveFlag=true.
  4. 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

  1. GET /system/members?page=1&pageSize=1000&conditions=inactiveFlag=false to retrieve all active members.
  2. Paginate through results incrementing the page parameter until the response array is empty or fewer than pageSize records are returned.
  3. Map ConnectWise member fields (identifier, emailAddress, firstName, lastName, role) to the external directory schema.
  4. For incremental sync, store the last sync timestamp and filter with conditions=lastUpdated>[{ISO8601 timestamp}] on subsequent runs.
  5. 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.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 9, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

Abnormal Security logo

Abnormal Security

API Only
AutomationAPI only
Last updatedMar 2026

Abnormal Security is an enterprise email security platform focused on detecting and investigating threats such as phishing, account takeover (ATO), and vendor email compromise. It does not support SCIM provisioning, which means every app in your stack

ActiveCampaign logo

ActiveCampaign

API Only
AutomationAPI only
Last updatedFeb 2026

ActiveCampaign uses a group-based permission model: every user belongs to exactly one group, and all feature-area access (Contacts, Campaigns, Automations, Deals, Reports, Templates) is configured at the group level, not per individual. The default Adm

ADP logo

ADP

API Only
AutomationAPI only
Last updatedFeb 2026

ADP Workforce Now is a mid-market to enterprise HCM platform that serves as the HR source of record for employee data — payroll, benefits, time, and talent. User access is governed by a hybrid permission model: predefined security roles (Security Maste