Repository: amp-labs/connectors
Pull request #1643 (2025-05-07 16:10:51)

[CON-1604] feat: Added write functionality for the instantly V2 connector
### Sample Response

**For lead-labels**
![image](https://github.com/user-attachments/assets/61f2e79e-e744-453e-bfb5-14a49a0efa3e)

**For custom-tags**
![image](https://github.com/user-attachments/assets/ab9ac64f-b811-4e37-bba6-655023c941c8)

**For lead-lists**
![image](https://github.com/user-attachments/assets/02cbcf6e-2b42-4641-8c18-bd73d5e9d167)

### Testcase result
![image](https://github.com/user-attachments/assets/29f5981c-30d4-4048-8c80-366b19583b41)



Patch:
diff --git a/providers/instantlyai/README.md b/providers/instantlyai/README.md
index 18b14b80..1d34d58b 100644
--- a/providers/instantlyai/README.md
+++ b/providers/instantlyai/README.md
@@ -5,27 +5,36 @@
 Below is an exhaustive list of objects & methods supported on the objects
 
 InstantlyAI API version: v2
-------------------------------------------------------------------
-| Object                    | Resource                   | Method |
-| Accounts                  | accounts                   | read   |
-| Campaigns                 | campaigns                  | read   |
-| Emails                    | emails                     | read   |
-| Lead-lists                | lead-lists                 | read   |
-| Inbox-placement-tests     | inbox-placement-tests      | read   |
-| Background-jobs           | background-jobs            | read   |
-| Custom-tags               | custom-tags                | read   |
-| Block-lists-entries       | block-lists-entries        | read   |
-| Lead-labels               | lead-labels                | read   |
-| Workspace-group-members   | workspace-group-members    | read   |
-| Workspace-members         | workspace-members          | read   |
-| Lead                      | leads/list                 | read   |
-| Campaigns analytics       | campaigns/analytics        | read   |
-| Campaigns analytics daily | campaigns/analytics/daily  | read   |
-| Campaigns analytics steps | campaigns/analytics/steps  | read   |
-| Email service provider    | inbox-placement-tests/email| read   |
+------------------------------------------------------------------------------
+| Object                     | Resource                    | Method          |
+| Accounts                   | accounts                    | read and write  |
+| Campaigns                  | campaigns                   | read and write  |
+| Emails                     | emails                      | read and write  |
+| Lead-lists                 | lead-lists                  | read and write  |
+| Inbox-placement-tests      | inbox-placement-tests       | read and write  |
+| Background-jobs            | background-jobs             | read            |
+| Custom-tags                | custom-tags                 | read and write  |
+| Block-lists-entries        | block-lists-entries         | read and write  |
+| Lead-labels                | lead-labels                 | read and write  |
+| Workspace-group-members    | workspace-group-members     | read and write  |
+| Workspace-members          | workspace-members           | read and write  |
+| Lead                       | leads/list                  | read            |
+| Campaigns analytics        | campaigns/analytics         | read            |
+| Campaigns analytics daily  | campaigns/analytics/daily   | read            | 
+| Campaigns analytics steps  | campaigns/analytics/steps   | read            |
+| Email service provider     | inbox-placement-tests/email | read            |
                               -service-provider-options  
-| Audit logs                | audit-logs                 | read   |                              
-------------------------------------------------------------------
+| Audit logs                 | audit-logs                  | read            |                   
+| Email verification         | email-verification          | write           |
+| Leads merge                | leads/merge                 | write           |
+| Update interest status     | leads/update-interest-status| write           | 
+| Leads subsequence remove   | leads/subsequence/remove    | write           |
+| Leads move                 | leads/move                  | write           |
+| Leads export               | leads/export                | write           |
+| Leads subsequence move     | leads/subsequence/move      | write           |
+| Custom-tags toggle-resource| custom-tags/toggle-resource | write           |
+| Workspaces current         | workspaces/current          | write           |
+------------------------------------------------------------------------------
 
 Note:
  - The "Lead" object uses the POST API method to retrieve metadata, unlike the GET method typically used for such read actions.
diff --git a/providers/instantlyai/connector.go b/providers/instantlyai/connector.go
index 7e266278..c8c8f3dc 100644
--- a/providers/instantlyai/connector.go
+++ b/providers/instantlyai/connector.go
@@ -6,9 +6,11 @@ import (
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/interpreter"
 	"github.com/amp-labs/connectors/internal/components"
+	"github.com/amp-labs/connectors/internal/components/deleter"
 	"github.com/amp-labs/connectors/internal/components/operations"
 	"github.com/amp-labs/connectors/internal/components/reader"
 	"github.com/amp-labs/connectors/internal/components/schema"
+	"github.com/amp-labs/connectors/internal/components/writer"
 	"github.com/amp-labs/connectors/providers"
 	"github.com/amp-labs/connectors/providers/instantlyai/metadata"
 )
@@ -23,6 +25,8 @@ type Connector struct {
 	// Supported operations
 	components.SchemaProvider
 	components.Reader
+	components.Writer
+	components.Deleter
 }
 
 const apiVersion = "v2"
@@ -56,5 +60,31 @@ func constructor(base *components.Connector) (*Connector, error) {
 		},
 	)
 
+	connector.Writer = writer.NewHTTPWriter(
+		connector.HTTPClient().Client,
+		registry,
+		connector.ProviderContext.Module(),
+		operations.WriteHandlers{
+			BuildRequest:  connector.buildWriteRequest,
+			ParseResponse: connector.parseWriteResponse,
+			ErrorHandler: interpreter.ErrorHandler{
+				JSON: interpreter.NewFaultyResponder(errorFormats, nil),
+			}.Handle,
+		},
+	)
+
+	connector.Deleter = deleter.NewHTTPDeleter(
+		connector.HTTPClient().Client,
+		registry,
+		connector.ProviderContext.Module(),
+		operations.DeleteHandlers{
+			BuildRequest:  connector.buildDeleteRequest,
+			ParseResponse: connector.parseDeleteResponse,
+			ErrorHandler: interpreter.ErrorHandler{
+				JSON: interpreter.NewFaultyResponder(errorFormats, nil),
+			}.Handle,
+		},
+	)
+
 	return connector, nil
 }
diff --git a/providers/instantlyai/handlers.go b/providers/instantlyai/handlers.go
index e8d0edb7..8328c590 100644
--- a/providers/instantlyai/handlers.go
+++ b/providers/instantlyai/handlers.go
@@ -3,12 +3,15 @@ package instantlyai
 import (
 	"bytes"
 	"context"
+	"encoding/json"
+	"fmt"
 	"net/http"
 	"strconv"
 	"time"
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 )
 
 func (c *Connector) buildReadRequest(ctx context.Context, params common.ReadParams) (*http.Request, error) {
@@ -60,3 +63,96 @@ func (c *Connector) parseReadResponse(
 		params.Fields,
 	)
 }
+
+func (c *Connector) buildWriteRequest(ctx context.Context, params common.WriteParams) (*http.Request, error) {
+	url, err := urlbuilder.New(c.ProviderInfo().BaseURL, apiVersion, params.ObjectName)
+	if err != nil {
+		return nil, err
+	}
+
+	method := http.MethodPost
+
+	if len(params.RecordId) > 0 {
+		url.AddPath(params.RecordId)
+
+		method = http.MethodPatch
+	}
+
+	jsonData, err := json.Marshal(params.RecordData)
+	if err != nil {
+		return nil, err
+	}
+
+	return http.NewRequestWithContext(ctx, method, url.String(), bytes.NewReader(jsonData))
+}
+
+func (c *Connector) parseWriteResponse(
+	ctx context.Context,
+	params common.WriteParams,
+	request *http.Request,
+	response *common.JSONHTTPResponse,
+) (*common.WriteResult, error) {
+	body, ok := response.Body()
+	if !ok {
+		return &common.WriteResult{ // nolint:nilerr
+			Success: true,
+		}, nil
+	}
+
+	recordID, err := jsonquery.New(body).StrWithDefault("id", "")
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := jsonquery.Convertor.ObjectToMap(body)
+	if err != nil {
+		return nil, err
+	}
+
+	return &common.WriteResult{
+		Success:  true,
+		RecordId: recordID,
+		Errors:   nil,
+		Data:     resp,
+	}, nil
+}
+
+func (c *Connector) buildDeleteRequest(ctx context.Context, params common.DeleteParams) (*http.Request, error) {
+	url, err := urlbuilder.New(c.ProviderInfo().BaseURL, apiVersion, params.ObjectName, params.RecordId)
+	if err != nil {
+		return nil, err
+	}
+
+	// For the delete functionality, the lead-labels endpoint requires an empty object ({}) in the body parameters,
+	// while other endpoints require a null value in the body parameters.
+	// Refer sample delete objects body params https://developer.instantly.ai/api/v2/account/deleteaccount.
+	body := []byte(`null`)
+
+	// Refer link for lead-labels object https://developer.instantly.ai/api/v2/leadlabel/deleteleadlabel.
+	if params.ObjectName == "lead-labels" {
+		body = []byte("{}")
+	}
+
+	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url.String(), bytes.NewReader(body))
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
+func (c *Connector) parseDeleteResponse(
+	ctx context.Context,
+	params common.DeleteParams,
+	request *http.Request,
+	resp *common.JSONHTTPResponse,
+) (*common.DeleteResult, error) {
+	if resp.Code != http.StatusOK {
+		return nil, fmt.Errorf("%w: failed to delete record: %d", common.ErrRequestFailed, resp.Code)
+	}
+
+	// A successful delete returns 200 OK
+	return &common.DeleteResult{
+		Success: true,
+	}, nil
+}
diff --git a/providers/instantlyai/supports.go b/providers/instantlyai/supports.go
index b4d20606..f3a2f626 100644
--- a/providers/instantlyai/supports.go
+++ b/providers/instantlyai/supports.go
@@ -12,12 +12,53 @@ import (
 func supportedOperations() components.EndpointRegistryInput {
 	readSupport := metadata.Schemas.ObjectNames().GetList(common.ModuleRoot)
 
+	writeSupport := []string{
+		"accounts",
+		"campaigns",
+		"emails",
+		"lead-lists",
+		"inbox-placement-tests",
+		"api-keys",
+		"leads",
+		"custom-tags",
+		"block-lists-entries",
+		"lead-labels",
+		"workspace-group-members",
+		"workspace-members",
+		"subsequences",
+		"email-verification",
+	}
+
+	deleteSupport := []string{
+		"accounts",
+		"campaigns",
+		"emails",
+		"lead-lists",
+		"inbox-placement-tests",
+		"api-keys",
+		"leads",
+		"custom-tags",
+		"block-lists-entries",
+		"lead-labels",
+		"workspace-group-members",
+		"workspace-members",
+		"subsequences",
+	}
+
 	return components.EndpointRegistryInput{
 		common.ModuleRoot: {
 			{
 				Endpoint: fmt.Sprintf("{%s}", strings.Join(readSupport, ",")),
 				Support:  components.ReadSupport,
 			},
+			{
+				Endpoint: fmt.Sprintf("{%s}", strings.Join(writeSupport, ",")),
+				Support:  components.WriteSupport,
+			},
+			{
+				Endpoint: fmt.Sprintf("{%s}", strings.Join(deleteSupport, ",")),
+				Support:  components.DeleteSupport,
+			},
 		},
 	}
 }


Test patch:
diff --git a/providers/instantlyai/delete_test.go b/providers/instantlyai/delete_test.go
new file mode 100644
index 00000000..913c917c
--- /dev/null
+++ b/providers/instantlyai/delete_test.go
@@ -0,0 +1,81 @@
+package instantlyai
+
+import (
+	"errors"
+	"net/http"
+	"testing"
+
+	"github.com/amp-labs/connectors"
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/test/utils/mockutils/mockcond"
+	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
+	"github.com/amp-labs/connectors/test/utils/testroutines"
+	"github.com/amp-labs/connectors/test/utils/testutils"
+)
+
+func TestDelete(t *testing.T) { // nolint:funlen,cyclop
+	t.Parallel()
+
+	errorNotFound := testutils.DataFromFile(t, "delete-missing-custom-tags.json")
+
+	tests := []testroutines.Delete{
+		{
+			Name:         "Delete object must be included",
+			Server:       mockserver.Dummy(),
+			ExpectedErrs: []error{common.ErrMissingObjects},
+		},
+		{
+			Name:         "Write object and its ID must be included",
+			Input:        common.DeleteParams{ObjectName: "custom-tags"},
+			Server:       mockserver.Dummy(),
+			ExpectedErrs: []error{common.ErrMissingRecordID},
+		},
+		{
+			Name:   "Object name is not supported",
+			Input:  common.DeleteParams{ObjectName: "background-jobs", RecordId: "675266e304a8e55b17f0228b"},
+			Server: mockserver.Dummy(),
+			ExpectedErrs: []error{
+				common.ErrOperationNotSupportedForObject,
+			},
+		},
+		{
+			Name:  "Successful delete",
+			Input: common.DeleteParams{ObjectName: "custom-tags", RecordId: "8cba2d22-cdd0-453f-ac69-4c43f71942e5"},
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/custom-tags/8cba2d22-cdd0-453f-ac69-4c43f71942e5"),
+					mockcond.MethodDELETE(),
+					mockcond.Body("null"),
+				},
+				Then: mockserver.Response(http.StatusOK),
+			}.Server(),
+			Expected:     &common.DeleteResult{Success: true},
+			ExpectedErrs: nil,
+		},
+		{
+			Name:  "Error on deleting missing record",
+			Input: common.DeleteParams{ObjectName: "custom-tags", RecordId: "4fa0ca9d-f205-4a68-8756-5ad62123a53a"},
+			Server: mockserver.Fixed{
+				Setup:  mockserver.ContentJSON(),
+				Always: mockserver.Response(http.StatusNotFound, errorNotFound),
+			}.Server(),
+			ExpectedErrs: []error{
+				common.ErrBadRequest,
+				errors.New( // nolint:goerr113
+					" Not Found",
+				),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.Name, func(t *testing.T) {
+			t.Parallel()
+
+			tt.Run(t, func() (connectors.DeleteConnector, error) {
+				return constructTestConnector(tt.Server.URL)
+			})
+		})
+	}
+}
diff --git a/providers/instantlyai/test/delete-missing-custom-tags.json b/providers/instantlyai/test/delete-missing-custom-tags.json
new file mode 100644
index 00000000..a58ba63e
--- /dev/null
+++ b/providers/instantlyai/test/delete-missing-custom-tags.json
@@ -0,0 +1,5 @@
+{
+    "statusCode": 404,
+    "error": "Not Found",
+    "message": "Tag not found"
+}
\ No newline at end of file
diff --git a/providers/instantlyai/test/write_accounts.json b/providers/instantlyai/test/write_accounts.json
new file mode 100644
index 00000000..2fabb302
--- /dev/null
+++ b/providers/instantlyai/test/write_accounts.json
@@ -0,0 +1,7 @@
+{
+    "email": "user@example.com",
+    "timestamp_created": "2025-04-29T21:41:55.421Z",
+    "timestamp_updated": "2025-04-29T21:41:55.421Z",
+    "first_name": "John",
+    "last_name": "Doe"
+  }
\ No newline at end of file
diff --git a/providers/instantlyai/test/write_custom_tags.json b/providers/instantlyai/test/write_custom_tags.json
new file mode 100644
index 00000000..0c4e8e3d
--- /dev/null
+++ b/providers/instantlyai/test/write_custom_tags.json
@@ -0,0 +1,8 @@
+{
+    "id": "0196837f-d659-7496-854c-84187bb6f708",
+    "timestamp_created": "2025-04-29T21:41:55.417Z",
+    "timestamp_updated": "2025-04-29T21:41:55.417Z",
+    "organization_id": "0196837f-d659-7496-854c-841903f3a321",
+    "label": "Important",
+    "description": "Used for marking important items"
+}
\ No newline at end of file
diff --git a/providers/instantlyai/write_test.go b/providers/instantlyai/write_test.go
new file mode 100644
index 00000000..156bf7b7
--- /dev/null
+++ b/providers/instantlyai/write_test.go
@@ -0,0 +1,119 @@
+package instantlyai
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/amp-labs/connectors"
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/test/utils/mockutils/mockcond"
+	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
+	"github.com/amp-labs/connectors/test/utils/testroutines"
+	"github.com/amp-labs/connectors/test/utils/testutils"
+)
+
+func TestWrite(t *testing.T) { // nolint:funlen,gocognit,cyclop,maintidx
+	t.Parallel()
+
+	customTagsResponse := testutils.DataFromFile(t, "write_custom_tags.json")
+	accountsResponse := testutils.DataFromFile(t, "write_accounts.json")
+
+	tests := []testroutines.Write{
+		{
+			Name:         "Write object must be included",
+			Server:       mockserver.Dummy(),
+			ExpectedErrs: []error{common.ErrMissingObjects},
+		},
+		{
+			Name:  "creating the custom tags",
+			Input: common.WriteParams{ObjectName: "custom-tags", RecordData: "dummy"},
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/custom-tags"),
+					mockcond.MethodPOST(),
+				},
+				Then: mockserver.Response(http.StatusOK, customTagsResponse),
+			}.Server(),
+			Expected: &common.WriteResult{
+				Success:  true,
+				RecordId: "0196837f-d659-7496-854c-84187bb6f708",
+				Errors:   nil,
+				Data: map[string]any{
+					"id":                "0196837f-d659-7496-854c-84187bb6f708",
+					"timestamp_created": "2025-04-29T21:41:55.417Z",
+					"timestamp_updated": "2025-04-29T21:41:55.417Z",
+					"organization_id":   "0196837f-d659-7496-854c-841903f3a321",
+					"label":             "Important",
+					"description":       "Used for marking important items",
+				},
+			},
+			ExpectedErrs: nil,
+		},
+		{
+			Name: "Update custom tags as PATCH",
+			Input: common.WriteParams{
+				ObjectName: "custom-tags",
+				RecordId:   "0196837f-d659-7496-854c-84187bb6f708",
+				RecordData: "dummy",
+			},
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/custom-tags/0196837f-d659-7496-854c-84187bb6f708"),
+					mockcond.MethodPATCH(),
+				},
+				Then: mockserver.Response(http.StatusOK, customTagsResponse),
+			}.Server(),
+			Expected: &common.WriteResult{
+				Success:  true,
+				RecordId: "0196837f-d659-7496-854c-84187bb6f708",
+				Errors:   nil,
+				Data: map[string]any{
+					"id":                "0196837f-d659-7496-854c-84187bb6f708",
+					"timestamp_created": "2025-04-29T21:41:55.417Z",
+					"timestamp_updated": "2025-04-29T21:41:55.417Z",
+					"organization_id":   "0196837f-d659-7496-854c-841903f3a321",
+					"label":             "Important",
+					"description":       "Used for marking important items",
+				},
+			},
+			ExpectedErrs: nil,
+		},
+		{
+			Name:  "Creating the accounts",
+			Input: common.WriteParams{ObjectName: "accounts", RecordData: "dummy"},
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/accounts"),
+					mockcond.MethodPOST(),
+				},
+				Then: mockserver.Response(http.StatusOK, accountsResponse),
+			}.Server(),
+			Expected: &common.WriteResult{
+				Success:  true,
+				RecordId: "",
+				Errors:   nil,
+				Data: map[string]any{
+					"email":             "user@example.com",
+					"timestamp_created": "2025-04-29T21:41:55.421Z",
+					"timestamp_updated": "2025-04-29T21:41:55.421Z",
+					"first_name":        "John",
+					"last_name":         "Doe",
+				},
+			},
+			ExpectedErrs: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.Name, func(t *testing.T) {
+			t.Parallel()
+
+			tt.Run(t, func() (connectors.WriteConnector, error) {
+				return constructTestConnector(tt.Server.URL)
+			})
+		})
+	}
+}
diff --git a/test/instantlyai/delete/delete.go b/test/instantlyai/delete/delete.go
new file mode 100644
index 00000000..f4f25c0f
--- /dev/null
+++ b/test/instantlyai/delete/delete.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log/slog"
+	"os"
+
+	"github.com/amp-labs/connectors/common"
+	ap "github.com/amp-labs/connectors/providers/instantlyai"
+	"github.com/amp-labs/connectors/test/instantlyai"
+)
+
+func main() {
+	os.Exit(MainFn())
+}
+
+func MainFn() int {
+	ctx := context.Background()
+
+	err := testCustomTags(ctx)
+	if err != nil {
+		return 1
+	}
+
+	err = testLeadLists(ctx)
+	if err != nil {
+		return 1
+	}
+
+	return 0
+}
+
+func testCustomTags(ctx context.Context) error {
+	conn := instantlyai.GetInstantlyAIConnector(ctx)
+
+	slog.Info("Deleting the custom tags")
+
+	deleteParams := common.DeleteParams{
+		ObjectName: "custom-tags",
+		RecordId:   "4fa0ca9d-f205-4a68-8756-5ad62123a53a",
+	}
+
+	res, err := Delete(ctx, conn, deleteParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(res); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func testLeadLists(ctx context.Context) error {
+	conn := instantlyai.GetInstantlyAIConnector(ctx)
+
+	slog.Info("Deleting the lead lists")
+
+	deleteParams := common.DeleteParams{
+		ObjectName: "lead-lists",
+		RecordId:   "3291573a-2eb9-4571-8e07-93d421774cc6",
+	}
+
+	res, err := Delete(ctx, conn, deleteParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(res); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func Delete(ctx context.Context, conn *ap.Connector, payload common.DeleteParams) (*common.DeleteResult, error) {
+	res, err := conn.Delete(ctx, payload)
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+// unmarshal the delte response.
+func constructResponse(res *common.DeleteResult) error {
+	jsonStr, err := json.MarshalIndent(res, "", " ")
+	if err != nil {
+		return fmt.Errorf("error marshalling JSON: %w", err)
+	}
+
+	_, _ = os.Stdout.Write(jsonStr)
+	_, _ = os.Stdout.WriteString("\n")
+
+	return nil
+}
diff --git a/test/instantlyai/write/write.go b/test/instantlyai/write/write.go
new file mode 100644
index 00000000..df755941
--- /dev/null
+++ b/test/instantlyai/write/write.go
@@ -0,0 +1,205 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log/slog"
+	"os"
+
+	"github.com/amp-labs/connectors/common"
+	ap "github.com/amp-labs/connectors/providers/instantlyai"
+	"github.com/amp-labs/connectors/test/instantlyai"
+)
+
+func main() {
+	os.Exit(MainFn())
+}
+
+func MainFn() int {
+	ctx := context.Background()
+
+	err := testLeadLabel(ctx)
+	if err != nil {
+		return 1
+	}
+
+	err = testCustomTags(ctx)
+	if err != nil {
+		return 1
+	}
+
+	err = testLeadLists(ctx)
+	if err != nil {
+		return 1
+	}
+
+	return 0
+}
+
+func testLeadLabel(ctx context.Context) error {
+	conn := instantlyai.GetInstantlyAIConnector(ctx)
+
+	slog.Info("Creating the lead labels")
+
+	writeParams := common.WriteParams{
+		ObjectName: "lead-labels",
+		RecordData: map[string]any{
+			"label":                 "Hot Lead",
+			"interest_status_label": "positive",
+		},
+		RecordId: "",
+	}
+
+	writeRes, err := Write(ctx, conn, writeParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(writeRes); err != nil {
+		return err
+	}
+
+	slog.Info("Updating the lead labels")
+
+	updateParams := common.WriteParams{
+		ObjectName: "lead-labels",
+		RecordData: map[string]any{
+			"label": "Lead",
+		},
+		RecordId: writeRes.RecordId,
+	}
+
+	res, err := Write(ctx, conn, updateParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(res); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func testCustomTags(ctx context.Context) error {
+	conn := instantlyai.GetInstantlyAIConnector(ctx)
+
+	slog.Info("Creating the custom tags")
+
+	writeParams := common.WriteParams{
+		ObjectName: "custom-tags",
+		RecordData: map[string]any{
+			"label": "Important",
+		},
+		RecordId: "",
+	}
+
+	writeRes, err := Write(ctx, conn, writeParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(writeRes); err != nil {
+		return err
+	}
+
+	slog.Info("Updating the custom tags")
+
+	updateParams := common.WriteParams{
+		ObjectName: "custom-tags",
+		RecordData: map[string]any{
+			"label": "Demo",
+		},
+		RecordId: writeRes.RecordId,
+	}
+
+	res, err := Write(ctx, conn, updateParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(res); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func testLeadLists(ctx context.Context) error {
+	conn := instantlyai.GetInstantlyAIConnector(ctx)
+
+	slog.Info("Creating the lead lists")
+
+	writeParams := common.WriteParams{
+		ObjectName: "lead-lists",
+		RecordData: map[string]any{
+			"name": "Lead List",
+		},
+		RecordId: "",
+	}
+
+	writeRes, err := Write(ctx, conn, writeParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(writeRes); err != nil {
+		return err
+	}
+
+	slog.Info("Updating the lead lists")
+
+	updateParams := common.WriteParams{
+		ObjectName: "lead-lists",
+		RecordData: map[string]any{
+			"name": "Demo",
+		},
+		RecordId: writeRes.RecordId,
+	}
+
+	res, err := Write(ctx, conn, updateParams)
+	if err != nil {
+		fmt.Println("ERR: ", err)
+
+		return err
+	}
+
+	if err := constructResponse(res); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func Write(ctx context.Context, conn *ap.Connector, payload common.WriteParams) (*common.WriteResult, error) {
+	res, err := conn.Write(ctx, payload)
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+// unmarshal the write response.
+func constructResponse(res *common.WriteResult) error {
+	jsonStr, err := json.MarshalIndent(res, "", " ")
+	if err != nil {
+		return fmt.Errorf("error marshalling JSON: %w", err)
+	}
+
+	_, _ = os.Stdout.Write(jsonStr)
+	_, _ = os.Stdout.WriteString("\n")
+
+	return nil
+}