From 3e36073f3c4efa80d9acafbc2ee224a6e00b8d26 Mon Sep 17 00:00:00 2001 From: Cobalt0s Date: Thu, 16 Jul 2026 00:15:38 +0300 Subject: [PATCH 1/4] [ENG-4125] feat(connectwise): Support Custom fields --- src/provider-guides/connectWise.mdx | 99 +++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 11 deletions(-) diff --git a/src/provider-guides/connectWise.mdx b/src/provider-guides/connectWise.mdx index 8d0ce36d..0ccf4cb0 100644 --- a/src/provider-guides/connectWise.mdx +++ b/src/provider-guides/connectWise.mdx @@ -251,20 +251,97 @@ Write payload example for the `contacts` object: } ``` -### Example integration - -To define an integration for ConnectWise, create a manifest file that looks like this: +### Custom Fields +The ConnectWise connector **supports reading and writing custom fields** on supported objects. +For reads, custom fields are available through scheduled reads, historical backfills, and subscriptions. +Each ConnectWise custom field is exposed as `customField` -- for example, +a field with ID `59` might represent a marketing email preference and be selected as `customField59`, while +a field with ID `83` might represent a hobby and be selected as `customField83`. +You can then map those fields to more readable names in your connector configuration, such as `marketingEmailOn` or `hobby`. ```YAML -# amp.yaml -specVersion: 1.0.0 -integrations: - - name: connectWise-integration - displayName: My ConnectWise Integration - provider: connectWise - proxy: - enabled: true +read: + objects: + - objectName: contacts + destination: connectWiseWebhook + requiredFields: + - fieldName: customField59 + mapToName: marketingEmailOn + - fieldName: customField83 + mapToName: hobby +``` +This produces output that is easier to work with: +```JSON +[ + { + "mappedFields":{ + "hobby": "Traveling", + "marketingEmailOn": true, + "firstName": "Estella" + } + } +] +``` + +**For writes**, custom fields can be provided as if it was a native standard field. +The connector translates these values into the ConnectWise API format automatically. + +To create a record with custom fields: +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "create", + "record": { + "firstName": "Estella", + "customField59": true, + "customField83": "Traveling" + } +} +``` +To update a record with custom fields using `PUT`: +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "update", + "record": { + "id": "58117", + "firstName": "Estella (UPDATED)", + "customField59": false, + "customField83": "Swimming" + } +} ``` +To update a record with custom fields using `PATCH`: +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "update", + "record": { + "id": "58117", + "patch": [ + {"op": "replace", "path": "/firstName", "value": "Jeniffer"}, + {"op": "replace", "path": "/customField59", "value": true}, + {"op": "replace", "path": "/customField83", "value": "Skiing"} + ] + } +} +``` + +When updating records, you only need to provide the custom fields you want to set or change. +You **do not** need to list **every** custom field exhaustively. +However, if you want to clear a field, then set its value to `null`, for example: +```JSON +{ + "customField83": null +} +``` + +When you are unsure of a **custom field ID**, check a **read response** for that object. +Custom fields are returned with their ConnectWise IDs in the `customFields` array. + +### Example integration + +For an example manifest file of a ConnectWise integration, visit our [samples repo on Github](https://github.com/amp-labs/samples/blob/main/connectwise/amp.yaml). ## Using the connector From 7d1b365a844d5a630b8da1f156f6072229df1c05 Mon Sep 17 00:00:00 2001 From: Cobalt0s Date: Tue, 21 Jul 2026 01:52:40 +0300 Subject: [PATCH 2/4] [ENG-4127] feat(connectwise): Communication items --- src/provider-guides/connectWise.mdx | 379 ++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) diff --git a/src/provider-guides/connectWise.mdx b/src/provider-guides/connectWise.mdx index 0ccf4cb0..c9661356 100644 --- a/src/provider-guides/connectWise.mdx +++ b/src/provider-guides/connectWise.mdx @@ -343,6 +343,385 @@ Custom fields are returned with their ConnectWise IDs in the `customFields` arra For an example manifest file of a ConnectWise integration, visit our [samples repo on Github](https://github.com/amp-labs/samples/blob/main/connectwise/amp.yaml). +### Contact Communication Items + +The `contacts` object contains communication details in a nested `communicationItems` array. +Each item represents a communication type, such as a particular email, phone number, or fax number. + +For example, a contact may contain several email addresses: + +```JSON +{ + "communicationItems": [ + { + "type": { "id": "14" }, + "value": "old@example.com", + "defaultFlag": false, + "communicationType": "Email" + }, + { + "type": { "id": "15" }, + "value": "john@example.com", + "defaultFlag": true, + "communicationType": "Email" + } + ] +} +``` + +To make the default communication details easier to read and write, the connector exposes virtual fields on the `contacts` object. +These fields are not native ConnectWise fields. They are a connector-level abstraction over the `communicationItems` array. + +| Virtual field | Description | +|----------------------------|-------------------------------------------------------| +| `AMPERSAND-defaultEmail` | The value of the default **email** communication item | +| `AMPERSAND-defaultEmailId` | The communication **type ID** of the default email | +| `AMPERSAND-defaultPhone` | The value of the default **phone** communication item | +| `AMPERSAND-defaultPhoneId` | The communication **type ID** of the default phone | +| `AMPERSAND-defaultFax` | The value of the default **fax** communication item | +| `AMPERSAND-defaultFaxId` | The communication **type ID** of the default fax | + +Only the default item for each communication category is exposed through these virtual fields. +The remaining communication items continue to exist in the underlying `communicationItems` array. + +### Reading default communication fields + +`Contacts.Read` supports these virtual fields. +When a contact is read, the connector examines its `communicationItems` array and exposes the item whose `defaultFlag` is `true` for each communication category. +For example, the following communication items: + +```JSON +[ + { + "type": { "id": "14" }, + "value": "old@example.com", + "defaultFlag": false, + "communicationType": "Email" + }, + { + "type": { "id": "15" }, + "value": "john@example.com", + "defaultFlag": true, + "communicationType": "Email" + } +] +``` + +are exposed as: + +```JSON +{ + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15" +} +``` + +The `AMPERSAND-defaultEmailId` field identifies which communication type the default email refers to. +This is important when a contact has multiple communication items in the same category. + +The same behavior applies to phone and fax communication items: + +```JSON +{ + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15", + "AMPERSAND-defaultPhone": "+1-555-0100", + "AMPERSAND-defaultPhoneId": "21", + "AMPERSAND-defaultFax": "+1-555-0199", + "AMPERSAND-defaultFaxId": "32" +} +``` + +These fields can be selected in a read operation just like other contact fields. + +```yaml +read: + objects: + - objectName: contacts + requiredFields: + - fieldName: AMPERSAND-defaultEmail +``` + +#### Writing communication fields + +The virtual fields can also be used when creating or updating contacts. + +The general pattern is: + +* `AMPERSAND-default` contains the value. +* `AMPERSAND-defaultId` identifies the communication type to which the value belongs. +* If the value is provided without an ID, the connector looks up the default communication type for that category. +* If the ID is provided, the connector uses that exact communication type. + +For example: + +```JSON +{ + "firstName": "John", + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15" +} +``` + +means: + +> Set the communication item with type ID `15` to `john@example.com` and make it the default email. + +The same record can set the default email, phone, and fax: + +```JSON +{ + "firstName": "John", + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15", + "AMPERSAND-defaultPhone": "+1-555-0100", + "AMPERSAND-defaultPhoneId": "21", + "AMPERSAND-defaultFax": "+1-555-0199", + "AMPERSAND-defaultFaxId": "32" +} +``` + +#### Create + +When creating a contact, provide the virtual fields in the record: + +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "create", + "record": { + "firstName": "John", + "lastName": "Smith", + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15", + "AMPERSAND-defaultPhone": "+1-555-0100", + "AMPERSAND-defaultPhoneId": "21" + } +} +``` + +The connector translates these virtual fields into ConnectWise `communicationItems` automatically. +This explicitly targets communication type `15` for an email, +regardless of which communication type is currently configured as the company's default. + +#### Update with `PUT` + +A regular update payload uses `PUT` and represents a full replacement of the record. + +For example: + +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "update", + "record": { + "id": "58117", + "firstName": "John", + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15", + "AMPERSAND-defaultPhone": "+1-555-0100", + "AMPERSAND-defaultPhoneId": "21" + } +} +``` + +The connector translates the virtual fields into a new `communicationItems` array. + +Because this is a full replacement, the existing communication items are cleared before the replacement is applied. +The new array contains the communication items represented by the virtual fields in the update payload. + +As a result, a `PUT` update should be treated as a replacement of the contact's communication items, not as a partial modification. + +> **Important:** Because the existing communication items are cleared before the replacement is applied, a failure between these operations could theoretically leave the contact without its previous communication items. This is an unlikely failure scenario, but it is a consequence of the ConnectWise API behavior required to perform this update. + +### Update with `PATCH` + +For partial updates, use a JSON Patch document. + +The virtual communication fields can be used in patch operations just like ordinary fields: + +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "update", + "record": { + "id": "58117", + "patch": [ + { + "op": "replace", + "path": "/AMPERSAND-defaultEmail", + "value": "john@example.com" + } + ] + } +} +``` + +The connector translates this operation into the appropriate update to the underlying `communicationItems` array. + +#### Changing the default value + +To change the value of the current default email, replace the value field: + +```JSON +{ + "op": "replace", + "path": "/AMPERSAND-defaultEmail", + "value": "john@example.com" +} +``` + +This updates the value of the current default email communication item. + +#### Changing both the value and communication type + +You can also provide the communication type ID in the same patch: + +```JSON +{ + "groupRef": "{{connectWiseGroupID}}", + "type": "update", + "record": { + "id": "58117", + "patch": [ + { + "op": "replace", + "path": "/AMPERSAND-defaultEmail", + "value": "john@example.com" + }, + { + "op": "replace", + "path": "/AMPERSAND-defaultEmailId", + "value": "15" + } + ] + } +} +``` + +This means: + +> Make communication type `15` the default email and set its value to `john@example.com`. + +If communication type `15` already exists, its existing value is updated and it becomes the default email. The previous default email is not necessarily deleted; it simply stops being the default. + +Conceptually, each communication type ID identifies one possible item within the corresponding category: + +```text +Email +├── Type ID 14 → old@example.com +└── Type ID 15 → john@example.com ← default + +AMPERSAND-defaultEmail → john@example.com +AMPERSAND-defaultEmailId → 15 +``` + +The virtual fields let you manipulate this single representative default item without having to address the underlying array directly. + +#### Setting a different existing item as the default + +Suppose a contact currently has: + +```JSON +[ + { + "type": { "id": "14" }, + "value": "old@example.com", + "defaultFlag": true, + "communicationType": "Email" + }, + { + "type": { "id": "15" }, + "value": "john@example.com", + "defaultFlag": false, + "communicationType": "Email" + } +] +``` + +The following patch: + +```JSON +[ + { + "op": "replace", + "path": "/AMPERSAND-defaultEmailId", + "value": "15" + }, + { + "op": "replace", + "path": "/AMPERSAND-defaultEmail", + "value": "new@example.com" + } +] +``` + +makes type `15` the default email and changes its value: + +```JSON +[ + { + "type": { "id": "14" }, + "value": "old@example.com", + "defaultFlag": false, + "communicationType": "Email" + }, + { + "type": { "id": "15" }, + "value": "new@example.com", + "defaultFlag": true, + "communicationType": "Email" + } +] +``` + +The old communication item remains in the category, but is no longer the default. + +#### Removing a default communication item + +You can remove a default communication item with a `remove` operation: + +```JSON +{ + "op": "remove", + "path": "/AMPERSAND-defaultEmail" +} +``` + +This removes the underlying communication item that is currently the default email. + +If another communication item exists in the same category, ConnectWise may automatically assign `defaultFlag=true` to another item. That item then becomes the new default email. + +For example: + +```text +Before: +Email +├── Type ID 14 → old@example.com ← default +└── Type ID 15 → backup@example.com + +After removing the default: +Email +└── Type ID 15 → backup@example.com ← may become default +``` + +If no other communication item exists in the category, the category becomes empty. + +### Choosing between `PUT` and `PATCH` + +Use `PUT` when you want to provide a full replacement record, including the communication items represented by the virtual fields. + +Use `PATCH` when you want to make a targeted change, such as: + +* Change the current default email value. +* Change the current default phone value. +* Change the current default fax value. +* Make a specific existing communication type the default. +* Remove the current default communication item. + +In most cases, `PATCH` is the safer choice for modifying one communication item because it changes only the requested communication item rather than replacing the entire `communicationItems` collection. + + ## Using the connector This connector uses Basic Auth, which means that you do not need to set up a Provider App before getting started. (Provider apps are only required for providers that use OAuth2 Authorization Code grant type.) From c65f7ddb6bd7acfd36e6da057aa377212fc2abd7 Mon Sep 17 00:00:00 2001 From: Cobalt0s Date: Tue, 21 Jul 2026 02:10:31 +0300 Subject: [PATCH 3/4] save --- src/provider-guides/connectWise.mdx | 115 +++++++--------------------- 1 file changed, 29 insertions(+), 86 deletions(-) diff --git a/src/provider-guides/connectWise.mdx b/src/provider-guides/connectWise.mdx index c9661356..c346f6fb 100644 --- a/src/provider-guides/connectWise.mdx +++ b/src/provider-guides/connectWise.mdx @@ -384,7 +384,7 @@ These fields are not native ConnectWise fields. They are a connector-level abstr Only the default item for each communication category is exposed through these virtual fields. The remaining communication items continue to exist in the underlying `communicationItems` array. -### Reading default communication fields +#### Reading default communication fields `Contacts.Read` supports these virtual fields. When a contact is read, the connector examines its `communicationItems` array and exposes the item whose `defaultFlag` is `true` for each communication category. @@ -453,34 +453,6 @@ The general pattern is: * If the value is provided without an ID, the connector looks up the default communication type for that category. * If the ID is provided, the connector uses that exact communication type. -For example: - -```JSON -{ - "firstName": "John", - "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15" -} -``` - -means: - -> Set the communication item with type ID `15` to `john@example.com` and make it the default email. - -The same record can set the default email, phone, and fax: - -```JSON -{ - "firstName": "John", - "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15", - "AMPERSAND-defaultPhone": "+1-555-0100", - "AMPERSAND-defaultPhoneId": "21", - "AMPERSAND-defaultFax": "+1-555-0199", - "AMPERSAND-defaultFaxId": "32" -} -``` - #### Create When creating a contact, provide the virtual fields in the record: @@ -493,13 +465,15 @@ When creating a contact, provide the virtual fields in the record: "firstName": "John", "lastName": "Smith", "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15", - "AMPERSAND-defaultPhone": "+1-555-0100", - "AMPERSAND-defaultPhoneId": "21" + "AMPERSAND-defaultEmailId": "15" } } ``` +means: + +> Set the email communication item with type ID `15` to `john@example.com` and make it the default email. + The connector translates these virtual fields into ConnectWise `communicationItems` automatically. This explicitly targets communication type `15` for an email, regardless of which communication type is currently configured as the company's default. @@ -507,34 +481,14 @@ regardless of which communication type is currently configured as the company's #### Update with `PUT` A regular update payload uses `PUT` and represents a full replacement of the record. - -For example: - -```JSON -{ - "groupRef": "{{connectWiseGroupID}}", - "type": "update", - "record": { - "id": "58117", - "firstName": "John", - "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15", - "AMPERSAND-defaultPhone": "+1-555-0100", - "AMPERSAND-defaultPhoneId": "21" - } -} -``` - The connector translates the virtual fields into a new `communicationItems` array. +`PUT` update should be treated as a replacement of the contact's communication items, not as a partial modification. -Because this is a full replacement, the existing communication items are cleared before the replacement is applied. -The new array contains the communication items represented by the virtual fields in the update payload. +> **Important:** Because the existing communication items are cleared before the replacement is applied, +a failure between these operations could theoretically leave the contact without its previous communication items. +This is an unlikely failure scenario, but it is a consequence of the ConnectWise API behavior required to perform this update. -As a result, a `PUT` update should be treated as a replacement of the contact's communication items, not as a partial modification. - -> **Important:** Because the existing communication items are cleared before the replacement is applied, a failure between these operations could theoretically leave the contact without its previous communication items. This is an unlikely failure scenario, but it is a consequence of the ConnectWise API behavior required to perform this update. - -### Update with `PATCH` +#### Update with `PATCH` For partial updates, use a JSON Patch document. @@ -603,20 +557,23 @@ This means: > Make communication type `15` the default email and set its value to `john@example.com`. -If communication type `15` already exists, its existing value is updated and it becomes the default email. The previous default email is not necessarily deleted; it simply stops being the default. +If communication type `15` already exists, its value is updated, and it is marked as the default email. +If the previous default email has a different type ID, that communication item is not deleted; +it simply stops being the default. + +Conceptually, each communication type ID identifies a specific item within the corresponding communication category: -Conceptually, each communication type ID identifies one possible item within the corresponding category: ```text Email -├── Type ID 14 → old@example.com -└── Type ID 15 → john@example.com ← default +├── Type ID 14 -> old@example.com +└── Type ID 15 -> john@example.com <- default -AMPERSAND-defaultEmail → john@example.com -AMPERSAND-defaultEmailId → 15 +AMPERSAND-defaultEmail -> john@example.com +AMPERSAND-defaultEmailId -> 15 ``` -The virtual fields let you manipulate this single representative default item without having to address the underlying array directly. +The virtual fields **let you manipulate this single representative default item** without having to address the underlying array directly. #### Setting a different existing item as the default @@ -656,7 +613,7 @@ The following patch: ] ``` -makes type `15` the default email and changes its value: +makes type `15` the default email instead of `14` and changes its value: ```JSON [ @@ -675,7 +632,7 @@ makes type `15` the default email and changes its value: ] ``` -The old communication item remains in the category, but is no longer the default. +The old communication item remains in the category, **but is no longer the default**. #### Removing a default communication item @@ -690,37 +647,23 @@ You can remove a default communication item with a `remove` operation: This removes the underlying communication item that is currently the default email. -If another communication item exists in the same category, ConnectWise may automatically assign `defaultFlag=true` to another item. That item then becomes the new default email. +If another communication item exists in the same category, ConnectWise will automatically assign `defaultFlag=true` +to another item. That item then becomes the new default email. For example: ```text Before: Email -├── Type ID 14 → old@example.com ← default -└── Type ID 15 → backup@example.com +├── Type ID 14 -> old@example.com <- default +└── Type ID 15 -> backup@example.com After removing the default: Email -└── Type ID 15 → backup@example.com ← may become default +└── Type ID 15 -> backup@example.com <- will become new default ``` -If no other communication item exists in the category, the category becomes empty. - -### Choosing between `PUT` and `PATCH` - -Use `PUT` when you want to provide a full replacement record, including the communication items represented by the virtual fields. - -Use `PATCH` when you want to make a targeted change, such as: - -* Change the current default email value. -* Change the current default phone value. -* Change the current default fax value. -* Make a specific existing communication type the default. -* Remove the current default communication item. - -In most cases, `PATCH` is the safer choice for modifying one communication item because it changes only the requested communication item rather than replacing the entire `communicationItems` collection. - +If no other communication item exists in the category, the category becomes empty and `null` values will be returned. ## Using the connector From 888dc8e9885ae023807c2f25410cf3b711bc7c96 Mon Sep 17 00:00:00 2001 From: Anusha Talasila Date: Tue, 21 Jul 2026 17:43:50 -0700 Subject: [PATCH 4/4] docs(connectwise): trim custom fields + communication items to builder-facing style Rewrite both sections to match the Copper/Insightly house style: lead with the field/naming convention and one example, move raw JSON Patch mechanics into a short advanced note. Communication-items write section describes upsert behavior (pending the flat-update fix). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/provider-guides/connectWise.mdx | 398 ++++------------------------ 1 file changed, 53 insertions(+), 345 deletions(-) diff --git a/src/provider-guides/connectWise.mdx b/src/provider-guides/connectWise.mdx index c346f6fb..a48aac4b 100644 --- a/src/provider-guides/connectWise.mdx +++ b/src/provider-guides/connectWise.mdx @@ -253,91 +253,37 @@ Write payload example for the `contacts` object: ### Custom Fields -The ConnectWise connector **supports reading and writing custom fields** on supported objects. -For reads, custom fields are available through scheduled reads, historical backfills, and subscriptions. -Each ConnectWise custom field is exposed as `customField` -- for example, -a field with ID `59` might represent a marketing email preference and be selected as `customField59`, while -a field with ID `83` might represent a hobby and be selected as `customField83`. -You can then map those fields to more readable names in your connector configuration, such as `marketingEmailOn` or `hobby`. -```YAML +The connector supports reading and writing custom fields on supported objects, and they behave like native fields. For reads, they're available through scheduled reads, historical backfills, and subscriptions. + +Each custom field is exposed as `customField`. Map it to a more readable name in your configuration with `mapToName`. + +> Example: a field with ID `59` representing a marketing preference is selected as `customField59` and can be mapped to `marketingEmailOn`. + +```yaml read: objects: - objectName: contacts - destination: connectWiseWebhook requiredFields: - fieldName: customField59 mapToName: marketingEmailOn - fieldName: customField83 mapToName: hobby ``` -This produces output that is easier to work with: -```JSON -[ - { - "mappedFields":{ - "hobby": "Traveling", - "marketingEmailOn": true, - "firstName": "Estella" - } - } -] -``` -**For writes**, custom fields can be provided as if it was a native standard field. -The connector translates these values into the ConnectWise API format automatically. +For writes, provide custom fields as if they were native fields; the connector converts them to the ConnectWise format automatically: -To create a record with custom fields: -```JSON -{ - "groupRef": "{{connectWiseGroupID}}", - "type": "create", - "record": { - "firstName": "Estella", - "customField59": true, - "customField83": "Traveling" - } -} -``` -To update a record with custom fields using `PUT`: -```JSON -{ - "groupRef": "{{connectWiseGroupID}}", - "type": "update", - "record": { - "id": "58117", - "firstName": "Estella (UPDATED)", - "customField59": false, - "customField83": "Swimming" - } -} -``` -To update a record with custom fields using `PATCH`: -```JSON +```json { - "groupRef": "{{connectWiseGroupID}}", - "type": "update", - "record": { - "id": "58117", - "patch": [ - {"op": "replace", "path": "/firstName", "value": "Jeniffer"}, - {"op": "replace", "path": "/customField59", "value": true}, - {"op": "replace", "path": "/customField83", "value": "Skiing"} - ] - } + "firstName": "Estella", + "customField59": true, + "customField83": "Traveling" } ``` -When updating records, you only need to provide the custom fields you want to set or change. -You **do not** need to list **every** custom field exhaustively. -However, if you want to clear a field, then set its value to `null`, for example: -```JSON -{ - "customField83": null -} -``` +You only need to include the custom fields you want to set or change. To clear a field, set its value to `null`. + +To find a custom field's ID, check a read response, custom fields are returned with their IDs in the `customFields` array. -When you are unsure of a **custom field ID**, check a **read response** for that object. -Custom fields are returned with their ConnectWise IDs in the `customFields` array. ### Example integration @@ -345,94 +291,22 @@ For an example manifest file of a ConnectWise integration, visit our [samples re ### Contact Communication Items -The `contacts` object contains communication details in a nested `communicationItems` array. -Each item represents a communication type, such as a particular email, phone number, or fax number. +ConnectWise stores a contact's email, phone, and fax numbers inside a nested `communicationItems` array, which isn't directly mappable. To make these easy to read and write, the connector exposes the **default** email, phone, and fax of a contact as flat fields you can map like any other contact field. -For example, a contact may contain several email addresses: +| Field | Description | +|----------------------------|------------------------------------------| +| `AMPERSAND-defaultEmail` | The contact's default email address | +| `AMPERSAND-defaultEmailId` | The communication type of that email | +| `AMPERSAND-defaultPhone` | The contact's default phone number | +| `AMPERSAND-defaultPhoneId` | The communication type of that phone | +| `AMPERSAND-defaultFax` | The contact's default fax number | +| `AMPERSAND-defaultFaxId` | The communication type of that fax | -```JSON -{ - "communicationItems": [ - { - "type": { "id": "14" }, - "value": "old@example.com", - "defaultFlag": false, - "communicationType": "Email" - }, - { - "type": { "id": "15" }, - "value": "john@example.com", - "defaultFlag": true, - "communicationType": "Email" - } - ] -} -``` - -To make the default communication details easier to read and write, the connector exposes virtual fields on the `contacts` object. -These fields are not native ConnectWise fields. They are a connector-level abstraction over the `communicationItems` array. - -| Virtual field | Description | -|----------------------------|-------------------------------------------------------| -| `AMPERSAND-defaultEmail` | The value of the default **email** communication item | -| `AMPERSAND-defaultEmailId` | The communication **type ID** of the default email | -| `AMPERSAND-defaultPhone` | The value of the default **phone** communication item | -| `AMPERSAND-defaultPhoneId` | The communication **type ID** of the default phone | -| `AMPERSAND-defaultFax` | The value of the default **fax** communication item | -| `AMPERSAND-defaultFaxId` | The communication **type ID** of the default fax | - -Only the default item for each communication category is exposed through these virtual fields. -The remaining communication items continue to exist in the underlying `communicationItems` array. - -#### Reading default communication fields - -`Contacts.Read` supports these virtual fields. -When a contact is read, the connector examines its `communicationItems` array and exposes the item whose `defaultFlag` is `true` for each communication category. -For example, the following communication items: - -```JSON -[ - { - "type": { "id": "14" }, - "value": "old@example.com", - "defaultFlag": false, - "communicationType": "Email" - }, - { - "type": { "id": "15" }, - "value": "john@example.com", - "defaultFlag": true, - "communicationType": "Email" - } -] -``` - -are exposed as: - -```JSON -{ - "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15" -} -``` - -The `AMPERSAND-defaultEmailId` field identifies which communication type the default email refers to. -This is important when a contact has multiple communication items in the same category. +Only the **default** item of each category is exposed. If a contact has several emails, only the one marked as default appears in `AMPERSAND-defaultEmail`. -The same behavior applies to phone and fax communication items: +#### Reading -```JSON -{ - "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15", - "AMPERSAND-defaultPhone": "+1-555-0100", - "AMPERSAND-defaultPhoneId": "21", - "AMPERSAND-defaultFax": "+1-555-0199", - "AMPERSAND-defaultFaxId": "32" -} -``` - -These fields can be selected in a read operation just like other contact fields. +Map these fields in a read action just like standard contact fields: ```yaml read: @@ -442,228 +316,62 @@ read: - fieldName: AMPERSAND-defaultEmail ``` -#### Writing communication fields - -The virtual fields can also be used when creating or updating contacts. - -The general pattern is: - -* `AMPERSAND-default` contains the value. -* `AMPERSAND-defaultId` identifies the communication type to which the value belongs. -* If the value is provided without an ID, the connector looks up the default communication type for that category. -* If the ID is provided, the connector uses that exact communication type. +A contact whose default email is `john@example.com` reads back as: -#### Create - -When creating a contact, provide the virtual fields in the record: - -```JSON +```json { - "groupRef": "{{connectWiseGroupID}}", - "type": "create", - "record": { - "firstName": "John", - "lastName": "Smith", - "AMPERSAND-defaultEmail": "john@example.com", - "AMPERSAND-defaultEmailId": "15" - } + "AMPERSAND-defaultEmail": "john@example.com", + "AMPERSAND-defaultEmailId": "15" } ``` -means: - -> Set the email communication item with type ID `15` to `john@example.com` and make it the default email. +The `...Id` field identifies which communication type the default value belongs to. This matters when a contact has more than one item in the same category. -The connector translates these virtual fields into ConnectWise `communicationItems` automatically. -This explicitly targets communication type `15` for an email, -regardless of which communication type is currently configured as the company's default. +#### Writing -#### Update with `PUT` +Map a value to `AMPERSAND-defaultEmail` (or phone/fax) to set it when creating or updating a contact: -A regular update payload uses `PUT` and represents a full replacement of the record. -The connector translates the virtual fields into a new `communicationItems` array. -`PUT` update should be treated as a replacement of the contact's communication items, not as a partial modification. +- Set just the value, and the connector uses the customer's default type for that category. +- Set the matching `...Id` field too if you want to target a specific type (for example, a Work email vs a Home email). The value is applied to that type and marked as the default. -> **Important:** Because the existing communication items are cleared before the replacement is applied, -a failure between these operations could theoretically leave the contact without its previous communication items. -This is an unlikely failure scenario, but it is a consequence of the ConnectWise API behavior required to perform this update. +An update changes only the fields you send. Anything you don't send is left untouched, so setting the email does not affect the phone, fax, or other contact fields. -#### Update with `PATCH` - -For partial updates, use a JSON Patch document. - -The virtual communication fields can be used in patch operations just like ordinary fields: - -```JSON -{ - "groupRef": "{{connectWiseGroupID}}", - "type": "update", - "record": { - "id": "58117", - "patch": [ - { - "op": "replace", - "path": "/AMPERSAND-defaultEmail", - "value": "john@example.com" - } - ] - } -} -``` +For example, to set a contact's email on create: -The connector translates this operation into the appropriate update to the underlying `communicationItems` array. - -#### Changing the default value - -To change the value of the current default email, replace the value field: - -```JSON +```json { - "op": "replace", - "path": "/AMPERSAND-defaultEmail", - "value": "john@example.com" + "firstName": "John", + "lastName": "Smith", + "AMPERSAND-defaultEmail": "john@example.com" } ``` -This updates the value of the current default email communication item. + +The `...Id` fields refer to ConnectWise "communication types" configured in the customer's ConnectWise account. You only need them when a contact has more than one email, phone, or fax and you want to target a specific one. + -#### Changing both the value and communication type +#### Advanced: partial updates with JSON Patch -You can also provide the communication type ID in the same patch: +If you call the write API directly, you can send a JSON Patch to change a single field, including these virtual fields: -```JSON +```json { - "groupRef": "{{connectWiseGroupID}}", "type": "update", "record": { "id": "58117", "patch": [ - { - "op": "replace", - "path": "/AMPERSAND-defaultEmail", - "value": "john@example.com" - }, - { - "op": "replace", - "path": "/AMPERSAND-defaultEmailId", - "value": "15" - } + { "op": "replace", "path": "/AMPERSAND-defaultEmail", "value": "john@example.com" } ] } } ``` -This means: - -> Make communication type `15` the default email and set its value to `john@example.com`. - -If communication type `15` already exists, its value is updated, and it is marked as the default email. -If the previous default email has a different type ID, that communication item is not deleted; -it simply stops being the default. - -Conceptually, each communication type ID identifies a specific item within the corresponding communication category: +- `replace` on a value updates the current default item. +- `replace` on an `...Id` field switches which communication type is the default. +- `remove` deletes the current default item for that category. +The connector translates these into the correct changes on the underlying `communicationItems` array. -```text -Email -├── Type ID 14 -> old@example.com -└── Type ID 15 -> john@example.com <- default - -AMPERSAND-defaultEmail -> john@example.com -AMPERSAND-defaultEmailId -> 15 -``` - -The virtual fields **let you manipulate this single representative default item** without having to address the underlying array directly. - -#### Setting a different existing item as the default - -Suppose a contact currently has: - -```JSON -[ - { - "type": { "id": "14" }, - "value": "old@example.com", - "defaultFlag": true, - "communicationType": "Email" - }, - { - "type": { "id": "15" }, - "value": "john@example.com", - "defaultFlag": false, - "communicationType": "Email" - } -] -``` - -The following patch: - -```JSON -[ - { - "op": "replace", - "path": "/AMPERSAND-defaultEmailId", - "value": "15" - }, - { - "op": "replace", - "path": "/AMPERSAND-defaultEmail", - "value": "new@example.com" - } -] -``` - -makes type `15` the default email instead of `14` and changes its value: - -```JSON -[ - { - "type": { "id": "14" }, - "value": "old@example.com", - "defaultFlag": false, - "communicationType": "Email" - }, - { - "type": { "id": "15" }, - "value": "new@example.com", - "defaultFlag": true, - "communicationType": "Email" - } -] -``` - -The old communication item remains in the category, **but is no longer the default**. - -#### Removing a default communication item - -You can remove a default communication item with a `remove` operation: - -```JSON -{ - "op": "remove", - "path": "/AMPERSAND-defaultEmail" -} -``` - -This removes the underlying communication item that is currently the default email. - -If another communication item exists in the same category, ConnectWise will automatically assign `defaultFlag=true` -to another item. That item then becomes the new default email. - -For example: - -```text -Before: -Email -├── Type ID 14 -> old@example.com <- default -└── Type ID 15 -> backup@example.com - -After removing the default: -Email -└── Type ID 15 -> backup@example.com <- will become new default -``` - -If no other communication item exists in the category, the category becomes empty and `null` values will be returned. ## Using the connector