Repository: amp-labs/connectors
Pull request #1380 (2025-02-19 10:31:01)

[CON-1221] feat: Add Join Me connector
## Checklist
- [x] Created PR from non-main branch

## Notes
It supports batching process.

## Testing
### GET ALL
URL: http://localhost:4444/v1/meetings
![image](https://github.com/user-attachments/assets/676f545f-5c6a-4208-b77c-87cf920d354b)

### GET
URL: http://localhost:4444/v1/meetings/18755422
![image](https://github.com/user-attachments/assets/28bd73e1-b680-4525-899d-1a01ba03a31f)

### POST
URL: http://localhost:4444/v1/meetings
![image](https://github.com/user-attachments/assets/86c641dc-6678-446c-ac5d-a66fbdaac653)

### PATCH
URL: http://localhost:4444/v1/meetings/18755422
![image](https://github.com/user-attachments/assets/8cc3fdc5-ef01-4a9a-8ae5-8370e2d74d77)

### DELETE
URL: http://localhost:4444/v1/meetings/18755422
![image](https://github.com/user-attachments/assets/cef63eb0-806f-404e-994c-20471579711d)

### Token
![image](https://github.com/user-attachments/assets/df72aece-994e-4f3b-9288-8920a7808f44)



Patch:
diff --git a/common/jsonquery/queries.go b/common/jsonquery/queries.go
deleted file mode 100644
index 611b7a12..00000000
--- a/common/jsonquery/queries.go
+++ /dev/null
@@ -1,170 +0,0 @@
-package jsonquery
-
-import (
-	"math"
-
-	"github.com/spyzhov/ajson"
-)
-
-// Query is a helpful wrapper of ajson library that adds errors when querying JSON payload.
-//
-// Usage examples, where node is JSON parsed via ajson library:
-//
-//	->	Must get *int64:					jsonquery.New(node).Integer("num", false)
-//	->	Optional *string:					jsonquery.New(node).String("text", true)
-//	->	Nested array:						jsonquery.New(node, "your", "path", "to", "array").Array("list", false)
-//	->	Convert current obj to list:		jsonquery.New(node).Array("", false)
-type Query struct {
-	node *ajson.Node
-	zoom []string
-}
-
-// New constructs query searching for key. Extra keys are preceding forming a zoom path.
-func New(node *ajson.Node, zoom ...string) *Query {
-	return &Query{
-		node: node,
-		zoom: zoom,
-	}
-}
-
-// Object returns json object.
-// Optional argument set to false will create error in case of missing value.
-// Empty key is interpreter as "this", in other words current node.
-func (q *Query) Object(key string, optional bool) (*ajson.Node, error) {
-	node, err := q.getInnerKey(key, optional)
-	if err != nil {
-		return nil, err
-	}
-
-	if node == nil {
-		return nil, nil // nolint:nilnil
-	}
-
-	if node.IsNull() {
-		return nil, handleNullNode(key, optional)
-	}
-
-	if !node.IsObject() {
-		return nil, ErrNotObject
-	}
-
-	return node, nil
-}
-
-// Integer returns integer.
-// Optional argument set to false will create error in case of missing value.
-// Empty key is interpreter as "this", in other words current node.
-func (q *Query) Integer(key string, optional bool) (*int64, error) {
-	node, err := q.getInnerKey(key, optional)
-	if err != nil {
-		return nil, err
-	}
-
-	if node == nil {
-		return nil, nil // nolint:nilnil
-	}
-
-	if node.IsNull() {
-		return nil, handleNullNode(key, optional)
-	}
-
-	count, err := node.GetNumeric()
-	if err != nil {
-		return nil, ErrNotNumeric
-	}
-
-	if math.Mod(count, 1.0) != 0 {
-		return nil, ErrNotInteger
-	}
-
-	result := int64(count)
-
-	return &result, nil
-}
-
-// Str returns string.
-// Optional argument set to false will create error in case of missing value.
-// Empty key is interpreter as "this", in other words current node.
-func (q *Query) Str(key string, optional bool) (*string, error) {
-	node, err := q.getInnerKey(key, optional)
-	if err != nil {
-		return nil, err
-	}
-
-	if node == nil {
-		return nil, nil // nolint:nilnil
-	}
-
-	if node.IsNull() {
-		return nil, handleNullNode(key, optional)
-	}
-
-	txt, err := node.GetString()
-	if err != nil {
-		return nil, ErrNotString
-	}
-
-	return &txt, nil
-}
-
-// Bool returns boolean.
-// Optional argument set to false will create error in case of missing value.
-// Empty key is interpreter as "this", in other words current node.
-func (q *Query) Bool(key string, optional bool) (*bool, error) {
-	node, err := q.getInnerKey(key, optional)
-	if err != nil {
-		return nil, err
-	}
-
-	if node == nil {
-		return nil, nil // nolint:nilnil
-	}
-
-	if node.IsNull() {
-		return nil, handleNullNode(key, optional)
-	}
-
-	flag, err := node.GetBool()
-	if err != nil {
-		return nil, ErrNotBool
-	}
-
-	return &flag, nil
-}
-
-// Array returns list of nodes.
-// Optional argument set to false will create error in case of missing value.
-// Empty key is interpreter as "this", in other words current node.
-func (q *Query) Array(key string, optional bool) ([]*ajson.Node, error) {
-	node, err := q.getInnerKey(key, optional)
-	if err != nil {
-		return nil, err
-	}
-
-	if node == nil {
-		return nil, nil // nolint:nilnil
-	}
-
-	if node.IsNull() {
-		return nil, handleNullNode(key, optional)
-	}
-
-	arr, err := node.GetArray()
-	if err != nil {
-		return nil, formatProblematicKeyError(key, ErrNotArray)
-	}
-
-	return arr, nil
-}
-
-// ArraySize returns the array size located under key.
-// It is assumed that array value must be not null and present.
-// Empty key is interpreter as "this", in other words current node.
-func (q *Query) ArraySize(key string) (int64, error) {
-	arr, err := q.Array(key, false)
-	if err != nil {
-		return 0, err
-	}
-
-	return int64(len(arr)), nil
-}
diff --git a/common/parse.go b/common/parse.go
index dee6353c..8a9ba6b9 100644
--- a/common/parse.go
+++ b/common/parse.go
@@ -3,8 +3,8 @@ package common
 import (
 	"strings"
 
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -117,7 +117,17 @@ func GetOptionalRecordsUnderJSONPath(jsonPath string, nestedPath ...string) Reco
 
 func getRecords(optional bool, jsonPath string, nestedPath ...string) RecordsFunc {
 	return func(node *ajson.Node) ([]map[string]any, error) {
-		arr, err := jsonquery.New(node, nestedPath...).Array(jsonPath, optional)
+		var (
+			arr []*ajson.Node
+			err error
+		)
+
+		if optional {
+			arr, err = jsonquery.New(node, nestedPath...).ArrayOptional(jsonPath)
+		} else {
+			arr, err = jsonquery.New(node, nestedPath...).ArrayRequired(jsonPath)
+		}
+
 		if err != nil {
 			return nil, err
 		}
diff --git a/common/types.go b/common/types.go
index cf0c75f4..0623739d 100644
--- a/common/types.go
+++ b/common/types.go
@@ -418,10 +418,24 @@ func inferDeprecatedFieldsMap(fields map[string]FieldMetadata) map[string]string
 type RegistrationResult struct {
 	RegistrationRef string
 	Result          any // struct depends on the provider
+	Status          RegistrationStatus
 }
 
+type RegistrationStatus string
+
+const (
+	// registration is pending and not yet complete.
+	RegistrationStatusPending RegistrationStatus = "pending"
+	// registration returned error, and all intermittent steps are rolled back.
+	RegistrationStatusFailed RegistrationStatus = "failed"
+	// successful registration.
+	RegistrationStatusSuccess RegistrationStatus = "success"
+	// registration returned error, and failed to rollback some intermittent steps.
+	RegistrationStatusError RegistrationStatus = "error"
+)
+
 type SubscriptionRegistrationParams struct {
-	Request any
+	Request any `json:"request" validate:"required"`
 }
 
 type ObjectEvents struct {
@@ -473,11 +487,11 @@ type SubscribeConnector interface {
 	UpdateRegistration(
 		ctx context.Context,
 		params SubscriptionRegistrationParams,
-		previousResult SubscriptionResult,
-	) (*SubscriptionResult, error)
+		previousResult RegistrationResult,
+	) (*RegistrationResult, error)
 	DeleteRegistration(
 		ctx context.Context,
-		previousResult SubscriptionResult,
+		previousResult RegistrationResult,
 	) error
 
 	Subscribe(
diff --git a/go.mod b/go.mod
index 3f04087f..f7679621 100644
--- a/go.mod
+++ b/go.mod
@@ -18,6 +18,7 @@ require (
 	github.com/spf13/viper v1.19.0
 	github.com/spyzhov/ajson v0.9.6
 	github.com/stretchr/testify v1.10.0
+	gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a
 	golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329
 	golang.org/x/oauth2 v0.26.0
 	golang.org/x/text v0.22.0
@@ -63,6 +64,6 @@ require (
 require (
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/v3 v3.5.2
 )
diff --git a/go.sum b/go.sum
index a8ae290c..d911c8a7 100644
--- a/go.sum
+++ b/go.sum
@@ -113,6 +113,8 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW
 github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a h1:DxppxFKRqJ8WD6oJ3+ZXKDY0iMONQDl5UTg2aTyHh8k=
+gitlab.com/c0b/go-ordered-json v0.0.0-20201030195603-febf46534d5a/go.mod h1:NREvu3a57BaK0R1+ztrEzHWiZAihohNLQ6trPxlIqZI=
 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
diff --git a/common/jsonquery/converters.go b/internal/jsonquery/converters.go
similarity index 100%
rename from common/jsonquery/converters.go
rename to internal/jsonquery/converters.go
diff --git a/common/jsonquery/defaults.go b/internal/jsonquery/defaults.go
similarity index 89%
rename from common/jsonquery/defaults.go
rename to internal/jsonquery/defaults.go
index db1a8d92..430ba6c7 100644
--- a/common/jsonquery/defaults.go
+++ b/internal/jsonquery/defaults.go
@@ -6,7 +6,7 @@ import (
 )
 
 func (q *Query) IntegerWithDefault(key string, defaultValue int64) (int64, error) {
-	result, err := q.Integer(key, true)
+	result, err := q.IntegerOptional(key)
 	if err != nil {
 		return 0, err
 	}
@@ -19,7 +19,7 @@ func (q *Query) IntegerWithDefault(key string, defaultValue int64) (int64, error
 }
 
 func (q *Query) StrWithDefault(key string, defaultValue string) (string, error) {
-	result, err := q.Str(key, true)
+	result, err := q.StringOptional(key)
 	if err != nil {
 		return "", err
 	}
@@ -44,7 +44,7 @@ func (q *Query) TextWithDefault(key string, defaultValue string) (string, error)
 		// Current data under `key` is not a string.
 		// Explore other data types.
 		// NOTE: as of now we check only if it is an integer.
-		number, err := q.Integer(key, true)
+		number, err := q.IntegerOptional(key)
 		if err != nil {
 			return "", err
 		}
@@ -60,7 +60,7 @@ func (q *Query) TextWithDefault(key string, defaultValue string) (string, error)
 }
 
 func (q *Query) BoolWithDefault(key string, defaultValue bool) (bool, error) {
-	result, err := q.Bool(key, true)
+	result, err := q.BoolOptional(key)
 	if err != nil {
 		return false, err
 	}
diff --git a/common/jsonquery/errors.go b/internal/jsonquery/errors.go
similarity index 100%
rename from common/jsonquery/errors.go
rename to internal/jsonquery/errors.go
diff --git a/common/jsonquery/nested.go b/internal/jsonquery/nested.go
similarity index 100%
rename from common/jsonquery/nested.go
rename to internal/jsonquery/nested.go
diff --git a/internal/jsonquery/queries.go b/internal/jsonquery/queries.go
new file mode 100644
index 00000000..71dbe283
--- /dev/null
+++ b/internal/jsonquery/queries.go
@@ -0,0 +1,233 @@
+package jsonquery
+
+import (
+	"math"
+
+	"github.com/spyzhov/ajson"
+)
+
+// Query is a helpful wrapper of ajson library that adds errors when querying JSON payload.
+//
+// Usage examples, where node is JSON parsed via ajson library:
+//
+//	->	Must get *int64:					jsonquery.New(node).Integer("num", false)
+//	->	Optional *string:					jsonquery.New(node).String("text", true)
+//	->	Nested array:						jsonquery.New(node, "your", "path", "to", "array").Array("list", false)
+//	->	Convert current obj to list:		jsonquery.New(node).Array("", false)
+type Query struct {
+	node *ajson.Node
+	zoom []string
+}
+
+// New constructs query searching for key. Extra keys are preceding forming a zoom path.
+func New(node *ajson.Node, zoom ...string) *Query {
+	return &Query{
+		node: node,
+		zoom: zoom,
+	}
+}
+
+// ObjectOptional returns node object if present.
+// If the entity at the key path is not a node object, an error is returned.
+// Empty key is interpreter as "this", in other words current node.
+func (q *Query) ObjectOptional(key string) (*ajson.Node, error) {
+	return q.internalQueryObject(key, true)
+}
+
+// ObjectRequired returns node object.
+// If the entity at the key path is not a node object or is missing, an error is returned.
+// Empty key is interpreter as "this", in other words current node.
+// Missing key returns ErrKeyNotFound. Null value returns ErrNullJSON.
+func (q *Query) ObjectRequired(key string) (*ajson.Node, error) {
+	return q.internalQueryObject(key, false)
+}
+
+func (q *Query) internalQueryObject(key string, optional bool) (*ajson.Node, error) {
+	node, err := q.getInnerKey(key, optional)
+	if err != nil {
+		return nil, err
+	}
+
+	if node == nil {
+		return nil, nil // nolint:nilnil
+	}
+
+	if node.IsNull() {
+		return nil, handleNullNode(key, optional)
+	}
+
+	if !node.IsObject() {
+		return nil, ErrNotObject
+	}
+
+	return node, nil
+}
+
+// IntegerOptional returns integer if present.
+// If the entity at the key path is not an integer, an error is returned.
+// Empty key is interpreter as "this", in other words current node.
+func (q *Query) IntegerOptional(key string) (*int64, error) {
+	return q.internalQueryInteger(key, true)
+}
+
+// IntegerRequired returns integer.
+// If the entity at the key path is not an integer or is missing, an error is returned.
+// Empty key is interpreter as "this", in other words current node.
+// Missing key returns ErrKeyNotFound. Null value returns ErrNullJSON.
+func (q *Query) IntegerRequired(key string) (int64, error) {
+	integer, err := q.internalQueryInteger(key, false)
+	if err != nil {
+		return 0, err
+	}
+
+	return *integer, nil
+}
+
+func (q *Query) internalQueryInteger(key string, optional bool) (*int64, error) {
+	node, err := q.getInnerKey(key, optional)
+	if err != nil {
+		return nil, err
+	}
+
+	if node == nil {
+		return nil, nil // nolint:nilnil
+	}
+
+	if node.IsNull() {
+		return nil, handleNullNode(key, optional)
+	}
+
+	count, err := node.GetNumeric()
+	if err != nil {
+		return nil, ErrNotNumeric
+	}
+
+	if math.Mod(count, 1.0) != 0 {
+		return nil, ErrNotInteger
+	}
+
+	result := int64(count)
+
+	return &result, nil
+}
+
+// StringOptional returns string if present.
+// If the entity at the key path is not a string, an error is returned.
+// Empty key is interpreted as "this", in other words a current node.
+func (q *Query) StringOptional(key string) (*string, error) {
+	return q.internalQueryString(key, true)
+}
+
+// StringRequired returns string.
+// If the entity at the key path is not a string or is missing, an error is returned.
+// Empty key is interpreted as "this", in other words a current node.
+// Missing key returns ErrKeyNotFound. Null value returns ErrNullJSON.
+func (q *Query) StringRequired(key string) (string, error) {
+	text, err := q.internalQueryString(key, false)
+	if err != nil {
+		return "", err
+	}
+
+	return *text, nil
+}
+
+func (q *Query) internalQueryString(key string, optional bool) (*string, error) {
+	node, err := q.getInnerKey(key, optional)
+	if err != nil {
+		return nil, err
+	}
+
+	if node == nil {
+		return nil, nil // nolint:nilnil
+	}
+
+	if node.IsNull() {
+		return nil, handleNullNode(key, optional)
+	}
+
+	txt, err := node.GetString()
+	if err != nil {
+		return nil, ErrNotString
+	}
+
+	return &txt, nil
+}
+
+// BoolOptional returns boolean if present.
+// If the entity at the key path is not a boolean, an error is returned.
+// Empty key is interpreted as "this", in other words a current node.
+func (q *Query) BoolOptional(key string) (*bool, error) {
+	return q.internalQueryBool(key, true)
+}
+
+// BoolRequired returns boolean.
+// If the entity at the key path is not a boolean or is missing, an error is returned.
+// Empty key is interpreted as "this", in other words a current node.
+// Missing key returns ErrKeyNotFound. Null value returns ErrNullJSON.
+func (q *Query) BoolRequired(key string) (bool, error) {
+	flag, err := q.internalQueryBool(key, false)
+	if err != nil {
+		return false, err
+	}
+
+	return *flag, nil
+}
+
+func (q *Query) internalQueryBool(key string, optional bool) (*bool, error) {
+	node, err := q.getInnerKey(key, optional)
+	if err != nil {
+		return nil, err
+	}
+
+	if node == nil {
+		return nil, nil // nolint:nilnil
+	}
+
+	if node.IsNull() {
+		return nil, handleNullNode(key, optional)
+	}
+
+	flag, err := node.GetBool()
+	if err != nil {
+		return nil, ErrNotBool
+	}
+
+	return &flag, nil
+}
+
+// ArrayOptional returns array of nodes if present.
+// If the entity at the key path is not an array, an error is returned.
+// Empty key is interpreted as "this", in other words a current node.
+func (q *Query) ArrayOptional(key string) ([]*ajson.Node, error) {
+	return q.internalQueryArray(key, true)
+}
+
+// ArrayRequired returns array of nodes.
+// If the entity at the key path is not an array or is missing, an error is returned.
+// Empty key is interpreted as "this", in other words a current node.
+// Missing key returns ErrKeyNotFound. Null value returns ErrNullJSON.
+func (q *Query) ArrayRequired(key string) ([]*ajson.Node, error) {
+	return q.internalQueryArray(key, false)
+}
+
+func (q *Query) internalQueryArray(key string, optional bool) ([]*ajson.Node, error) {
+	node, err := q.getInnerKey(key, optional)
+	if err != nil {
+		return nil, err
+	}
+
+	if node == nil {
+		return nil, nil // nolint:nilnil
+	}
+
+	if node.IsNull() {
+		return nil, handleNullNode(key, optional)
+	}
+
+	arr, err := node.GetArray()
+	if err != nil {
+		return nil, formatProblematicKeyError(key, ErrNotArray)
+	}
+
+	return arr, nil
+}
diff --git a/providers/apollo/metadata.go b/providers/apollo/metadata.go
index e665aa5d..11b02c75 100644
--- a/providers/apollo/metadata.go
+++ b/providers/apollo/metadata.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -67,7 +67,7 @@ func (c *Connector) ListObjectMetadata(ctx context.Context,
 func parseMetadataFromResponse(body *ajson.Node, objectName string) (*common.ObjectMetadata, error) {
 	objectName = constructSupportedObjectName(objectName)
 
-	arr, err := jsonquery.New(body).Array(objectName, true)
+	arr, err := jsonquery.New(body).ArrayOptional(objectName)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/apollo/parse.go b/providers/apollo/parse.go
index 3f35fbe3..25128dbb 100644
--- a/providers/apollo/parse.go
+++ b/providers/apollo/parse.go
@@ -4,7 +4,7 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -12,7 +12,7 @@ import (
 func getNextRecords(node *ajson.Node) (string, error) {
 	var nextPage string
 
-	pagination, err := jsonquery.New(node).Object("pagination", true)
+	pagination, err := jsonquery.New(node).ObjectOptional("pagination")
 	if err != nil {
 		return "", err
 	}
@@ -23,7 +23,7 @@ func getNextRecords(node *ajson.Node) (string, error) {
 			return "", err
 		}
 
-		totalPages, err := jsonquery.New(pagination).Integer("total_pages", true)
+		totalPages, err := jsonquery.New(pagination).IntegerOptional("total_pages")
 		if err != nil {
 			return "", err
 		}
@@ -39,7 +39,7 @@ func getNextRecords(node *ajson.Node) (string, error) {
 // recordsWrapperFunc returns the records using the objectName dynamically.
 func recordsWrapperFunc(obj string) common.RecordsFunc {
 	return func(node *ajson.Node) ([]map[string]any, error) {
-		result, err := jsonquery.New(node).Array(obj, true)
+		result, err := jsonquery.New(node).ArrayOptional(obj)
 		if err != nil {
 			return nil, err
 		}
@@ -55,7 +55,7 @@ func searchRecords(fld string) common.RecordsFunc {
 	fld = constructSupportedObjectName(fld)
 
 	return func(node *ajson.Node) ([]map[string]any, error) {
-		result, err := jsonquery.New(node).Array(fld, true)
+		result, err := jsonquery.New(node).ArrayOptional(fld)
 		if err != nil {
 			return nil, err
 		}
diff --git a/providers/apollo/write.go b/providers/apollo/write.go
index e6192ae6..0375d1e2 100644
--- a/providers/apollo/write.go
+++ b/providers/apollo/write.go
@@ -4,8 +4,8 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -53,7 +53,7 @@ func constructWriteResult(body *ajson.Node, objName string) (*common.WriteResult
 	// created/updated details in it.
 	obj := naming.NewSingularString(objName)
 
-	respObject, err := jsonquery.New(body).Object(obj.String(), false)
+	respObject, err := jsonquery.New(body).ObjectRequired(obj.String())
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/asana/parse.go b/providers/asana/parse.go
index 99f99f7f..6983328f 100644
--- a/providers/asana/parse.go
+++ b/providers/asana/parse.go
@@ -2,13 +2,13 @@ package asana
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 func makeNextRecordsURL() common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
-		nextPageURL, err := jsonquery.New(node, "next_page").Str("uri", true)
+		nextPageURL, err := jsonquery.New(node, "next_page").StringOptional("uri")
 		if err != nil {
 			return "", err
 		}
diff --git a/providers/asana/write.go b/providers/asana/write.go
index 0fda1894..3bdfd13d 100644
--- a/providers/asana/write.go
+++ b/providers/asana/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -51,7 +51,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
-	nested, err := jsonquery.New(body).Object("data", false)
+	nested, err := jsonquery.New(body).ObjectRequired("data")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/atlassian/authmetadata.go b/providers/atlassian/authmetadata.go
index 775976b7..365390c5 100644
--- a/providers/atlassian/authmetadata.go
+++ b/providers/atlassian/authmetadata.go
@@ -5,7 +5,7 @@ import (
 	"errors"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 )
 
 var (
@@ -75,19 +75,15 @@ func (c *Connector) retrieveCloudId(ctx context.Context) (string, error) {
 	}
 
 	for _, item := range arr {
-		workspaceName, err := jsonquery.New(item).Str("name", false)
+		workspaceName, err := jsonquery.New(item).StringRequired("name")
 		if err != nil {
 			return "", err
 		}
 
-		if *workspaceName == c.workspace {
-			// names match, select this container.
-			cloudId, err := jsonquery.New(item).Str("id", false)
-			if err != nil {
-				return "", err
-			}
-
-			return *cloudId, nil
+		if workspaceName == c.workspace {
+			// Names match, select this container.
+			// Returns cloudID.
+			return jsonquery.New(item).StringRequired("id")
 		}
 	}
 
diff --git a/providers/atlassian/metadataFields.go b/providers/atlassian/metadataFields.go
index e955ecad..ea28e989 100644
--- a/providers/atlassian/metadataFields.go
+++ b/providers/atlassian/metadataFields.go
@@ -3,7 +3,7 @@ package atlassian
 import (
 	"errors"
 
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -26,17 +26,17 @@ func (c *Connector) parseFieldsJiraIssue(node *ajson.Node) (map[string]string, e
 	fieldsMap := make(map[string]string)
 
 	for _, item := range arr {
-		name, err := jsonquery.New(item).Str("id", false)
+		name, err := jsonquery.New(item).StringRequired("id")
 		if err != nil {
 			return nil, err
 		}
 
-		displayName, err := jsonquery.New(item).Str("name", false)
+		displayName, err := jsonquery.New(item).StringRequired("name")
 		if err != nil {
 			return nil, err
 		}
 
-		fieldsMap[*name] = *displayName
+		fieldsMap[name] = displayName
 	}
 
 	return fieldsMap, nil
diff --git a/providers/atlassian/parse.go b/providers/atlassian/parse.go
index c9117d39..663a08ac 100644
--- a/providers/atlassian/parse.go
+++ b/providers/atlassian/parse.go
@@ -5,7 +5,7 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -31,7 +31,7 @@ Visual example of what will happen to each property:
 	}
 */
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	arr, err := jsonquery.New(node).Array("issues", false)
+	arr, err := jsonquery.New(node).ArrayRequired("issues")
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +39,7 @@ func getRecords(node *ajson.Node) ([]map[string]any, error) {
 	list := make([]map[string]any, len(arr))
 
 	for index, item := range arr {
-		fieldsObject, err := jsonquery.New(item).Object("fields", false)
+		fieldsObject, err := jsonquery.New(item).ObjectRequired("fields")
 		if err != nil {
 			return nil, errors.Join(common.ErrParseError, err)
 		}
@@ -49,13 +49,13 @@ func getRecords(node *ajson.Node) ([]map[string]any, error) {
 			return nil, errors.Join(common.ErrParseError, err)
 		}
 
-		id, err := jsonquery.New(item).Str("id", false)
+		id, err := jsonquery.New(item).StringRequired("id")
 		if err != nil {
 			return nil, errors.Join(common.ErrParseError, err)
 		}
 
 		// Enhance response with id property.
-		fields["id"] = *id
+		fields["id"] = id
 		list[index] = fields
 	}
 
@@ -77,7 +77,7 @@ func getNextRecords(node *ajson.Node) (string, error) {
 		return "", nil
 	}
 
-	startAt, err := jsonquery.New(node).Integer("startAt", true)
+	startAt, err := jsonquery.New(node).IntegerOptional("startAt")
 	if err != nil {
 		return "", err
 	}
diff --git a/providers/atlassian/write.go b/providers/atlassian/write.go
index 5250f872..52519249 100644
--- a/providers/atlassian/write.go
+++ b/providers/atlassian/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -52,14 +52,14 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
-	recordID, err := jsonquery.New(body).Str("id", false)
+	recordID, err := jsonquery.New(body).StringRequired("id")
 	if err != nil {
 		return nil, err
 	}
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     nil,
 	}, nil
diff --git a/providers/attio/parse.go b/providers/attio/parse.go
index 46de76aa..27d44eb6 100644
--- a/providers/attio/parse.go
+++ b/providers/attio/parse.go
@@ -4,8 +4,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -14,7 +14,7 @@ func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 		previousStart := 0
 
 		// Extract the data key value from the response.
-		value, err := jsonquery.New(node).Array("data", false)
+		value, err := jsonquery.New(node).ArrayRequired("data")
 		if err != nil {
 			return "", err
 		}
diff --git a/providers/attio/write.go b/providers/attio/write.go
index 06dba6f8..cf6448a6 100644
--- a/providers/attio/write.go
+++ b/providers/attio/write.go
@@ -6,8 +6,8 @@ import (
 	"errors"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -58,12 +58,12 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 func constructWriteResult(objName string, body *ajson.Node) (*common.WriteResult, error) {
 	obj := naming.NewSingularString(objName)
 
-	objectResponse, err := jsonquery.New(body).Object("data", false)
+	objectResponse, err := jsonquery.New(body).ObjectRequired("data")
 	if err != nil {
 		return nil, err
 	}
 
-	recordID, err := jsonquery.New(objectResponse, "id").Str(obj.String()+"_id", false)
+	recordID, err := jsonquery.New(objectResponse, "id").StringRequired(obj.String() + "_id")
 	if err != nil {
 		return nil, err
 	}
@@ -75,7 +75,7 @@ func constructWriteResult(objName string, body *ajson.Node) (*common.WriteResult
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     response,
 	}, nil
diff --git a/providers/chilipiper/parse.go b/providers/chilipiper/parse.go
index f530b656..850928e1 100644
--- a/providers/chilipiper/parse.go
+++ b/providers/chilipiper/parse.go
@@ -4,8 +4,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -24,12 +24,12 @@ func nextRecordsURL(url string) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
 		jsonQuery := jsonquery.New(node)
 
-		page, err := jsonQuery.Integer(pageKey, false)
+		page, err := jsonQuery.IntegerRequired(pageKey)
 		if err != nil {
 			return "", err
 		}
 
-		totalRecords, err := jsonQuery.Integer(totalKey, false)
+		totalRecords, err := jsonQuery.IntegerRequired(totalKey)
 		if err != nil {
 			return "", err
 		}
@@ -39,8 +39,8 @@ func nextRecordsURL(url string) common.NextPageFunc {
 			return "", err
 		}
 
-		if hasMorePages(pagesize, int(*page), int(*totalRecords)) {
-			pg := strconv.Itoa(int(*page + 1))
+		if hasMorePages(pagesize, int(page), int(totalRecords)) {
+			pg := strconv.Itoa(int(page + 1))
 
 			nextURL, err := urlbuilder.New(url)
 			if err != nil {
diff --git a/providers/closecrm/parse.go b/providers/closecrm/parse.go
index 60db595e..b00918f7 100644
--- a/providers/closecrm/parse.go
+++ b/providers/closecrm/parse.go
@@ -4,8 +4,8 @@ import (
 	"errors"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -34,7 +34,7 @@ var ErrSkipFailure = errors.New("error: failed to create next page url")
 func nextRecordsURL(url *urlbuilder.URL) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
 		// check if there is more items in the collection.
-		hasMore, err := jsonquery.New(node).Bool(hasMoreQuery, true)
+		hasMore, err := jsonquery.New(node).BoolOptional(hasMoreQuery)
 		if err != nil {
 			return "", err
 		}
@@ -66,7 +66,7 @@ Search Response schema:
 	}
 */
 func getNextRecordCursor(node *ajson.Node) (string, error) {
-	crs, err := jsonquery.New(node).Str("cursor", true)
+	crs, err := jsonquery.New(node).StringOptional("cursor")
 	if err != nil {
 		return "", err
 	}
diff --git a/providers/closecrm/write.go b/providers/closecrm/write.go
index 51fbf963..4209d744 100644
--- a/providers/closecrm/write.go
+++ b/providers/closecrm/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -48,7 +48,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(node *ajson.Node) (*common.WriteResult, error) {
-	recordID, err := jsonquery.New(node).Str("id", false)
+	recordID, err := jsonquery.New(node).StringRequired("id")
 	if err != nil {
 		return nil, err
 	}
@@ -60,7 +60,7 @@ func constructWriteResult(node *ajson.Node) (*common.WriteResult, error) {
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     data,
 	}, nil
diff --git a/providers/constantcontact/parse.go b/providers/constantcontact/parse.go
index 5a258753..821a28ed 100644
--- a/providers/constantcontact/parse.go
+++ b/providers/constantcontact/parse.go
@@ -2,7 +2,7 @@ package constantcontact
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/constantcontact/write.go b/providers/constantcontact/write.go
index 539e5bee..a3fbcc21 100644
--- a/providers/constantcontact/write.go
+++ b/providers/constantcontact/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -59,7 +59,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 func constructWriteResult(config common.WriteParams, body *ajson.Node) (*common.WriteResult, error) {
 	idFieldName := objectNameToWriteResponseIdentifier.Get(config.ObjectName)
 
-	recordID, err := jsonquery.New(body).Str(idFieldName, false)
+	recordID, err := jsonquery.New(body).StringRequired(idFieldName)
 	if err != nil {
 		return nil, err
 	}
@@ -71,7 +71,7 @@ func constructWriteResult(config common.WriteParams, body *ajson.Node) (*common.
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     data,
 	}, nil
diff --git a/providers/customerapp/parse.go b/providers/customerapp/parse.go
index cbf97fc6..6e467204 100644
--- a/providers/customerapp/parse.go
+++ b/providers/customerapp/parse.go
@@ -4,8 +4,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/customerapp/write.go b/providers/customerapp/write.go
index 60bbb45e..bd5c55b7 100644
--- a/providers/customerapp/write.go
+++ b/providers/customerapp/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -62,7 +62,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 func constructWriteResult(config common.WriteParams, body *ajson.Node) (*common.WriteResult, error) {
 	fieldName := ObjectNameToWriteResponseField.Get(config.ObjectName)
 
-	nested, err := jsonquery.New(body).Object(fieldName, false)
+	nested, err := jsonquery.New(body).ObjectRequired(fieldName)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/dynamicscrm/parse.go b/providers/dynamicscrm/parse.go
index a3e9f848..dd1cf3d9 100644
--- a/providers/dynamicscrm/parse.go
+++ b/providers/dynamicscrm/parse.go
@@ -1,12 +1,12 @@
 package dynamicscrm
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	arr, err := jsonquery.New(node).Array("value", false)
+	arr, err := jsonquery.New(node).ArrayRequired("value")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/gong/parse.go b/providers/gong/parse.go
index a0f59e56..8c6fb5ee 100644
--- a/providers/gong/parse.go
+++ b/providers/gong/parse.go
@@ -1,7 +1,7 @@
 package gong
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/gong/write.go b/providers/gong/write.go
index 334f56fd..9d7ea5b2 100644
--- a/providers/gong/write.go
+++ b/providers/gong/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -41,14 +41,14 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
-	recordID, err := jsonquery.New(body).Str("callId", false)
+	recordID, err := jsonquery.New(body).StringRequired("callId")
 	if err != nil {
 		return nil, err
 	}
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     nil,
 	}, nil
diff --git a/providers/hubspot/parse.go b/providers/hubspot/parse.go
index 0b094079..4880f9eb 100644
--- a/providers/hubspot/parse.go
+++ b/providers/hubspot/parse.go
@@ -5,7 +5,7 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/instantly/write.go b/providers/instantly/write.go
index 6aa618a2..405a53f7 100644
--- a/providers/instantly/write.go
+++ b/providers/instantly/write.go
@@ -4,9 +4,9 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/internal/goutils"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -110,14 +110,14 @@ func constructWriteResult(body *ajson.Node, recordIdLocation *string) (*common.W
 	}
 
 	// ID is integer that is always stored under different field name.
-	recordID, err := jsonquery.New(body).Str(*recordIdLocation, false)
+	recordID, err := jsonquery.New(body).StringRequired(*recordIdLocation)
 	if err != nil {
 		return nil, err
 	}
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     nil,
 	}, nil
diff --git a/providers/intercom/parse.go b/providers/intercom/parse.go
index c902b709..81c4ad58 100644
--- a/providers/intercom/parse.go
+++ b/providers/intercom/parse.go
@@ -5,8 +5,8 @@ import (
 	"strings"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/gertd/go-pluralize"
 	"github.com/spyzhov/ajson"
 )
@@ -38,7 +38,7 @@ func getRecords(node *ajson.Node) ([]map[string]any, error) {
 		return nil, err
 	}
 
-	arr, err := jsonquery.New(node).Array(arrKey, false)
+	arr, err := jsonquery.New(node).ArrayRequired(arrKey)
 	if err != nil {
 		return nil, err
 	}
@@ -59,7 +59,7 @@ func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 		}
 
 		// Probably, we are dealing with an object under `pages.next`
-		startingAfter, err := jsonquery.New(node, "pages", "next").Str("starting_after", true)
+		startingAfter, err := jsonquery.New(node, "pages", "next").StringOptional("starting_after")
 		if err != nil {
 			return "", err
 		}
@@ -95,7 +95,7 @@ func extractListFieldName(node *ajson.Node) (string, error) {
 	// default field at which list is stored
 	defaultFieldName := "data"
 
-	fieldName, err := jsonquery.New(node).Str("type", true)
+	fieldName, err := jsonquery.New(node).StringOptional("type")
 	if err != nil {
 		return "", err
 	}
diff --git a/providers/intercom/write.go b/providers/intercom/write.go
index 3fb4a8f7..83ff982d 100644
--- a/providers/intercom/write.go
+++ b/providers/intercom/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/iterable/parse.go b/providers/iterable/parse.go
index ac352bfd..3cda77ac 100644
--- a/providers/iterable/parse.go
+++ b/providers/iterable/parse.go
@@ -2,7 +2,7 @@ package iterable
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/amp-labs/connectors/providers/iterable/metadata"
 	"github.com/spyzhov/ajson"
 )
diff --git a/providers/iterable/write.go b/providers/iterable/write.go
index 67e27c63..00ad3d0e 100644
--- a/providers/iterable/write.go
+++ b/providers/iterable/write.go
@@ -7,9 +7,9 @@ import (
 	"strings"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/internal/datautils"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -138,16 +138,16 @@ func locateWriteRecordID(body *ajson.Node, objectName string) (string, error) {
 	}
 
 	// ID is integer that is always stored under different field name.
-	intIdentifier, err := jsonquery.New(body, recordIDLocation.zoom...).Integer(recordIDLocation.id, false)
+	intIdentifier, err := jsonquery.New(body, recordIDLocation.zoom...).IntegerRequired(recordIDLocation.id)
 	if err != nil {
 		return "", err
 	}
 
-	return strconv.FormatInt(*intIdentifier, 10), nil
+	return strconv.FormatInt(intIdentifier, 10), nil
 }
 
 func extractTemplateWriteRecordID(body *ajson.Node) (string, error) {
-	message, err := jsonquery.New(body).Str("msg", true)
+	message, err := jsonquery.New(body).StringOptional("msg")
 	if err != nil {
 		return "", err
 	}
diff --git a/providers/joinMe.go b/providers/joinMe.go
new file mode 100644
index 00000000..fea856c9
--- /dev/null
+++ b/providers/joinMe.go
@@ -0,0 +1,41 @@
+package providers
+
+const JoinMe Provider = "joinMe"
+
+func init() {
+	// JoinMe configuration
+	SetInfo(JoinMe, ProviderInfo{
+		DisplayName: "Join Me",
+		AuthType:    Oauth2,
+		BaseURL:     "https://api.join.me",
+		Oauth2Opts: &Oauth2Opts{
+			GrantType:                 AuthorizationCode,
+			AuthURL:                   "https://secure.join.me/api/public/v1/auth/oauth2",
+			TokenURL:                  "https://secure.join.me/api/public/v1/auth/token",
+			ExplicitScopesRequired:    true,
+			ExplicitWorkspaceRequired: false,
+		},
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+		Media: &Media{
+			DarkMode: &MediaTypeDarkMode{
+				IconURL: " https://res.cloudinary.com/dycvts6vp/image/upload/v1739958047/media/api.join.me_1739958046.jpg",
+				LogoURL: "https://res.cloudinary.com/dycvts6vp/image/upload/v1739958019/media/api.join.me_1739958017.png",
+			},
+			Regular: &MediaTypeRegular{
+				IconURL: " https://res.cloudinary.com/dycvts6vp/image/upload/v1739958047/media/api.join.me_1739958046.jpg",
+				LogoURL: "https://res.cloudinary.com/dycvts6vp/image/upload/v1739958019/media/api.join.me_1739958017.png",
+			},
+		},
+	})
+}
diff --git a/providers/keap/parse.go b/providers/keap/parse.go
index f25c8f0e..a6859a1d 100644
--- a/providers/keap/parse.go
+++ b/providers/keap/parse.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -25,7 +25,7 @@ func (c *Connector) parseReadRecords(
 	ctx context.Context, config common.ReadParams, jsonPath string,
 ) common.RecordsFunc {
 	return func(node *ajson.Node) ([]map[string]any, error) {
-		arr, err := jsonquery.New(node).Array(jsonPath, true)
+		arr, err := jsonquery.New(node).ArrayOptional(jsonPath)
 		if err != nil {
 			return nil, err
 		}
diff --git a/providers/keap/write.go b/providers/keap/write.go
index 20fa86ce..bd19cdbb 100644
--- a/providers/keap/write.go
+++ b/providers/keap/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -68,7 +68,7 @@ func constructWriteResult(
 	if config.ObjectName == objectNameFiles {
 		var err error
 		// Identifier is nested under "file_descriptor" object.
-		identifierHolder, err = jsonquery.New(body).Object("file_descriptor", false)
+		identifierHolder, err = jsonquery.New(body).ObjectRequired("file_descriptor")
 		if err != nil {
 			return nil, err
 		}
diff --git a/providers/kit/parse.go b/providers/kit/parse.go
index 0dc9baa6..306c0ac8 100644
--- a/providers/kit/parse.go
+++ b/providers/kit/parse.go
@@ -2,20 +2,20 @@ package kit
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
-		pagination, err := jsonquery.New(node).Object("pagination", true)
+		pagination, err := jsonquery.New(node).ObjectOptional("pagination")
 		if err != nil {
 			return "", err
 		}
 
 		if pagination != nil {
-			hasNextPage, err := jsonquery.New(pagination).Bool("has_next_page", true)
+			hasNextPage, err := jsonquery.New(pagination).BoolOptional("has_next_page")
 			if err != nil {
 				return "", err
 			}
@@ -24,7 +24,7 @@ func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 				return "", nil
 			}
 
-			endCursorToken, err := jsonquery.New(pagination).Str("end_cursor", true)
+			endCursorToken, err := jsonquery.New(pagination).StringOptional("end_cursor")
 			if err != nil {
 				return "", err
 			}
diff --git a/providers/kit/write.go b/providers/kit/write.go
index 8837e2fe..e08077f7 100644
--- a/providers/kit/write.go
+++ b/providers/kit/write.go
@@ -6,8 +6,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -56,12 +56,12 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 func constructWriteResult(objName string, body *ajson.Node) (*common.WriteResult, error) {
 	obj := naming.NewSingularString(objName).String()
 
-	objectResponse, err := jsonquery.New(body).Object(obj, true)
+	objectResponse, err := jsonquery.New(body).ObjectOptional(obj)
 	if err != nil {
 		return nil, err
 	}
 
-	recordID, err := jsonquery.New(objectResponse).Integer("id", true)
+	recordID, err := jsonquery.New(objectResponse).IntegerOptional("id")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/klaviyo/parse.go b/providers/klaviyo/parse.go
index badda859..d7d8f82a 100644
--- a/providers/klaviyo/parse.go
+++ b/providers/klaviyo/parse.go
@@ -1,7 +1,7 @@
 package klaviyo
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -25,7 +25,7 @@ import (
 //
 // The resulting fields for the above will be: type, id, test_account, contact_information, locale, links.
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	arr, err := jsonquery.New(node).Array("data", true)
+	arr, err := jsonquery.New(node).ArrayOptional("data")
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +39,7 @@ func flattenRecords(arr []*ajson.Node) ([]map[string]any, error) {
 	for index, element := range arr {
 		const keyAttributes = "attributes"
 
-		attributes, err := jsonquery.New(element).Object(keyAttributes, true)
+		attributes, err := jsonquery.New(element).ObjectOptional(keyAttributes)
 		if err != nil {
 			return nil, err
 		}
diff --git a/providers/klaviyo/write.go b/providers/klaviyo/write.go
index 0a4171bd..41937ac7 100644
--- a/providers/klaviyo/write.go
+++ b/providers/klaviyo/write.go
@@ -5,7 +5,7 @@ import (
 	"errors"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -125,7 +125,7 @@ func prepareWritePayload(config common.WriteParams) (any, error) {
 }
 
 func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
-	nested, err := jsonquery.New(body).Object("data", false)
+	nested, err := jsonquery.New(body).ObjectRequired("data")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/marketo/parse.go b/providers/marketo/parse.go
index e208c6b8..31c44ce1 100644
--- a/providers/marketo/parse.go
+++ b/providers/marketo/parse.go
@@ -4,7 +4,7 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -27,7 +27,7 @@ func constructNextRecordsURL(object string) common.NextPageFunc {
 func constructNextPageFilteredURL(node *ajson.Node) (string, error) {
 	jsonParser := jsonquery.New(node)
 
-	data, err := jsonParser.Array("result", false)
+	data, err := jsonParser.ArrayRequired("result")
 	if err != nil {
 		return "", err
 	}
@@ -36,12 +36,12 @@ func constructNextPageFilteredURL(node *ajson.Node) (string, error) {
 	// We'd have to check for the next page records, also due deletes the is also a probability of having more records
 	// even if the size do not reach 300.
 	if len(data) > 0 {
-		lastRecordID, err := jsonquery.New(data[len(data)-1]).Integer("id", false)
+		lastRecordID, err := jsonquery.New(data[len(data)-1]).IntegerRequired("id")
 		if err != nil {
 			return "", err
 		}
 
-		return strconv.Itoa(int(*lastRecordID) + 1), nil
+		return strconv.Itoa(int(lastRecordID) + 1), nil
 	}
 
 	return "", nil
@@ -49,7 +49,7 @@ func constructNextPageFilteredURL(node *ajson.Node) (string, error) {
 
 // getRecords returns the records from the response.
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	result, err := jsonquery.New(node).Array("result", true)
+	result, err := jsonquery.New(node).ArrayOptional("result")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/outreach/parse.go b/providers/outreach/parse.go
index 0a15d0be..2f1f4e32 100644
--- a/providers/outreach/parse.go
+++ b/providers/outreach/parse.go
@@ -3,14 +3,14 @@ package outreach
 import (
 	"encoding/json"
 
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 // getNextRecords returns the "next" url for the next page of results,
 // If available, else returns an empty string.
 func getNextRecordsURL(node *ajson.Node) (string, error) {
-	nextPageURL, err := jsonquery.New(node, "links").Str("next", true)
+	nextPageURL, err := jsonquery.New(node, "links").StringOptional("next")
 	if err != nil {
 		return "", err
 	}
diff --git a/providers/pipedrive/parse.go b/providers/pipedrive/parse.go
index f35e2e5e..e4eaa6da 100644
--- a/providers/pipedrive/parse.go
+++ b/providers/pipedrive/parse.go
@@ -4,8 +4,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -30,12 +30,12 @@ import (
 func nextRecordsURL(url *urlbuilder.URL) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
 		// check if there is more items in the collection.
-		more, err := jsonquery.New(node, "additional_data", "pagination").Bool("more_items_in_collection", true)
+		more, err := jsonquery.New(node, "additional_data", "pagination").BoolOptional("more_items_in_collection")
 		if err != nil {
 			return "", err
 		}
 
-		startValue, err := jsonquery.New(node, "additional_data", "pagination").Integer("next_start", true)
+		startValue, err := jsonquery.New(node, "additional_data", "pagination").IntegerOptional("next_start")
 		if err != nil {
 			return "", err
 		}
diff --git a/providers/pipeliner/parse.go b/providers/pipeliner/parse.go
index c87ff96c..1e2bd4cb 100644
--- a/providers/pipeliner/parse.go
+++ b/providers/pipeliner/parse.go
@@ -1,12 +1,12 @@
 package pipeliner
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	arr, err := jsonquery.New(node).Array("data", false)
+	arr, err := jsonquery.New(node).ArrayRequired("data")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/pipeliner/write.go b/providers/pipeliner/write.go
index 4c5126f9..e42aeadf 100644
--- a/providers/pipeliner/write.go
+++ b/providers/pipeliner/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -47,12 +47,12 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
-	success, err := jsonquery.New(body).Bool("success", false)
+	success, err := jsonquery.New(body).BoolRequired("success")
 	if err != nil {
 		return nil, err
 	}
 
-	nested, err := jsonquery.New(body).Object("data", false)
+	nested, err := jsonquery.New(body).ObjectRequired("data")
 	if err != nil {
 		return nil, err
 	}
@@ -68,7 +68,7 @@ func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
 	}
 
 	return &common.WriteResult{
-		Success:  *success,
+		Success:  success,
 		RecordId: recordID,
 		Errors:   nil,
 		Data:     data,
diff --git a/providers/salesforce/events.go b/providers/salesforce/events.go
index 02dbc7ff..0ebd5bc7 100644
--- a/providers/salesforce/events.go
+++ b/providers/salesforce/events.go
@@ -99,7 +99,7 @@ type NamedCredentialMetadata struct {
 	AllowMergeFieldsInBody      bool                        `json:"allowMergeFieldsInBody,omitempty"`
 	AllowMergeFieldsInHeader    bool                        `json:"allowMergeFieldsInHeader,omitempty"`
 	GenerateAuthorizationHeader bool                        `json:"generateAuthorizationHeader,omitempty"`
-	FullName                    string                      `json:"fullName,omitempty"                    validate:"required"`
+	FullName                    string                      `json:"fullName,omitempty"`
 	Label                       string                      `json:"label,omitempty"`
 	NamedCredentialParameters   []*NamedCredentialParameter `json:"namedCredentialParameters,omitempty"`
 	NamedCredentialType         string                      `json:"namedCredentialType,omitempty"`
@@ -154,6 +154,10 @@ func (c *Connector) CreateEventChannel(ctx context.Context, channel *EventChanne
 	return channel, nil
 }
 
+func (c *Connector) DeleteEventChannel(ctx context.Context, channelId string) (*common.JSONHTTPResponse, error) {
+	return c.deleteToSFAPI(ctx, "tooling/sobjects/PlatformEventChannel/"+channelId, "PlatformEventChannel")
+}
+
 // nolint: lll
 // https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/tooling_api_objects_platformeventchannelmember.htm
 func (c *Connector) CreateEventChannelMember(
@@ -170,6 +174,10 @@ func (c *Connector) CreateEventChannelMember(
 	return member, nil
 }
 
+func (c *Connector) DeleteEventChannelMember(ctx context.Context, memberId string) (*common.JSONHTTPResponse, error) {
+	return c.deleteToSFAPI(ctx, "tooling/sobjects/PlatformEventChannelMember/"+memberId, "EventChannelMember")
+}
+
 // nolint: lll
 // https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/tooling_api_objects_eventrelayconfig.htm?q=EventRelayConfig
 func (c *Connector) CreateEventRelayConfig(
@@ -186,6 +194,10 @@ func (c *Connector) CreateEventRelayConfig(
 	return cfg, nil
 }
 
+func (c *Connector) DeleteEventRelayConfig(ctx context.Context, cfgId string) (*common.JSONHTTPResponse, error) {
+	return c.deleteToSFAPI(ctx, "tooling/sobjects/EventRelayConfig/"+cfgId, "EventRelayConfig")
+}
+
 // RunEventRelay
 // nolint: lll
 // https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/tooling_api_objects_eventrelayconfig.htm?q=EventRelayConfig
@@ -205,11 +217,11 @@ func (c *Connector) RunEventRelay(ctx context.Context, cfg *EventRelayConfig) er
 	// patch returns no content with 204. If it fails, it will return an error.
 	_, err = c.Client.Patch(ctx, location.String(), config)
 	if err != nil {
-		slog.Error("Run EventRelayConfig", "error", err)
-
-		return err
+		return fmt.Errorf("error running event relay: %w", err)
 	}
 
+	cfg.Metadata.State = "RUN"
+
 	return nil
 }
 
@@ -255,6 +267,10 @@ func (c *Connector) CreateNamedCredential(ctx context.Context, creds *NamedCrede
 	return creds, nil
 }
 
+func (c *Connector) DeleteNamedCredential(ctx context.Context, credId string) (*common.JSONHTTPResponse, error) {
+	return c.deleteToSFAPI(ctx, "tooling/sobjects/NamedCredential/"+credId, "NamedCredential")
+}
+
 type Credential interface {
 	DestinationResourceName() string
 }
@@ -281,3 +297,17 @@ func (c *Connector) postToSFAPI(ctx context.Context, body any, path string, enti
 
 	return res, err
 }
+
+func (c *Connector) deleteToSFAPI(ctx context.Context, path string, entity string) (*common.JSONHTTPResponse, error) {
+	location, err := c.getRestApiURL(path)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.Client.Delete(ctx, location.String())
+	if err != nil {
+		return nil, fmt.Errorf("error deleting %s: %w", entity, err)
+	}
+
+	return resp, nil
+}
diff --git a/providers/salesforce/parse.go b/providers/salesforce/parse.go
index 1be9d9fd..01a13ce5 100644
--- a/providers/salesforce/parse.go
+++ b/providers/salesforce/parse.go
@@ -1,13 +1,13 @@
 package salesforce
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 // getRecords returns the records from the response.
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	records, err := jsonquery.New(node).Array("records", false)
+	records, err := jsonquery.New(node).ArrayRequired("records")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/salesforce/register.go b/providers/salesforce/register.go
new file mode 100644
index 00000000..d91478c1
--- /dev/null
+++ b/providers/salesforce/register.go
@@ -0,0 +1,267 @@
+package salesforce
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/logging"
+	"github.com/go-playground/validator"
+)
+
+var (
+	errInvalidRequestType = errors.New("invalid request type")
+	errMissingParams      = errors.New("missing required parameters")
+)
+
+type RegistrationParams struct {
+	// UniqueRef is a unique reference for the registration.
+	// It is used to create unique names for the Salesforce objects.
+	UniqueRef             string `json:"uniqueRef"             validate:"required"`
+	Label                 string `json:"label"                 validate:"required"`
+	AwsNamedCredentialArn string `json:"awsNamedCredentialArn" validate:"required"`
+}
+
+type ResultData struct {
+	EventChannel     *EventChannel
+	NamedCredential  *NamedCredential
+	EventRelayConfig *EventRelayConfig
+}
+
+func (c *Connector) rollbackRegister(ctx context.Context, res *ResultData) error {
+	if res.EventRelayConfig != nil {
+		_, err := c.DeleteEventRelayConfig(ctx, res.EventRelayConfig.Id)
+		if err != nil {
+			return fmt.Errorf("failed to delete event relay config: %w", err)
+		}
+	}
+
+	if res.NamedCredential != nil {
+		_, err := c.DeleteNamedCredential(ctx, res.NamedCredential.Id)
+		if err != nil {
+			logging.Logger(ctx).Error("failed to delete named credential", "error", err)
+
+			return fmt.Errorf("failed to delete named credential: %w", err)
+		}
+	}
+
+	if res.EventChannel != nil {
+		_, err := c.DeleteEventChannel(ctx, res.EventChannel.Id)
+		if err != nil {
+			logging.Logger(ctx).Error("failed to delete event channel", "error", err)
+
+			return fmt.Errorf("failed to delete event channel: %w", err)
+		}
+	}
+
+	return nil
+}
+
+func (c *Connector) Register(
+	ctx context.Context,
+	params *common.SubscriptionRegistrationParams,
+) (*common.RegistrationResult, error) {
+	validate := validator.New()
+	if err := validate.Struct(params); err != nil {
+		return nil, fmt.Errorf("invalid registration params: %w", err)
+	}
+
+	sfParams, ok := params.Request.(*RegistrationParams)
+	if !ok {
+		return nil, fmt.Errorf(
+			"%w: expected '%T', but received '%T'",
+			errInvalidRequestType,
+			sfParams,
+			params.Request,
+		)
+	}
+
+	result, err := c.register(ctx, sfParams)
+	if err != nil {
+		if rollbackErr := c.rollbackRegister(ctx, result); rollbackErr != nil {
+			return &common.RegistrationResult{
+				Status: common.RegistrationStatusError,
+			}, errors.Join(rollbackErr, err)
+		}
+
+		return &common.RegistrationResult{
+			Status: common.RegistrationStatusFailed,
+		}, err
+	}
+
+	return &common.RegistrationResult{
+		RegistrationRef: result.EventRelayConfig.Id,
+		Result:          result,
+		Status:          common.RegistrationStatusSuccess,
+	}, err
+}
+
+func (c *Connector) register(
+	ctx context.Context,
+	params *RegistrationParams,
+) (*ResultData, error) {
+	result := &ResultData{}
+
+	eventChannel, err := c.createEventChannel(ctx, params)
+	if err != nil {
+		return result, fmt.Errorf("failed to create event channel: %w", err)
+	}
+
+	result.EventChannel = eventChannel
+
+	namedCred, err := c.createNamedCredential(ctx, params)
+	if err != nil {
+		return result, fmt.Errorf("failed to create named credential: %w", err)
+	}
+
+	result.NamedCredential = namedCred
+
+	evtCfg, err := c.createEventRelayConfing(
+		ctx,
+		params,
+		namedCred.DestinationResourceName(),
+		eventChannel.FullName,
+	)
+	if err != nil {
+		return result, fmt.Errorf("failed to create event relay config: %w", err)
+	}
+
+	result.EventRelayConfig = evtCfg
+
+	if err := c.RunEventRelay(ctx, evtCfg); err != nil {
+		return result, fmt.Errorf("failed to run event relay: %w", err)
+	}
+
+	return result, nil
+}
+
+// DeleteRegistration will delete the Salesforce objects created during registration.
+// It will delete the EventRelayConfig, NamedCredential, and EventChannel.
+func (c *Connector) DeleteRegistration(ctx context.Context, registration *common.RegistrationResult) error {
+	if registration == nil {
+		return fmt.Errorf("%w: registration is null", errMissingParams)
+	}
+
+	if registration.Result == nil {
+		return fmt.Errorf("%w: registration result is null", errMissingParams)
+	}
+
+	validate := validator.New()
+	if err := validate.Struct(registration.Result); err != nil {
+		return fmt.Errorf("invalid registration result: %w", err)
+	}
+
+	result, ok := registration.Result.(*ResultData)
+	if !ok {
+		return fmt.Errorf(
+			"%w: expected result type '%T', but received '%T'",
+			errInvalidRequestType,
+			result,
+			registration.Result,
+		)
+	}
+
+	return c.rollbackRegister(ctx, result)
+}
+
+func (c *Connector) createEventChannel(ctx context.Context, params *RegistrationParams) (*EventChannel, error) {
+	channelName := GetChannelName(params.UniqueRef)
+
+	channel := &EventChannel{
+		FullName: channelName,
+		Metadata: &EventChannelMetadata{
+			ChannelType: "data",
+			Label:       params.UniqueRef,
+		},
+	}
+
+	return c.CreateEventChannel(ctx, channel)
+}
+
+func (c *Connector) createNamedCredential(ctx context.Context, params *RegistrationParams) (*NamedCredential, error) {
+	namedCred := &NamedCredential{
+		FullName: params.UniqueRef,
+		Metadata: &NamedCredentialMetadata{
+			GenerateAuthorizationHeader: true,
+			Label:                       params.Label,
+
+			// below are legacy fields
+			Endpoint:      params.AwsNamedCredentialArn,
+			PrincipalType: "NamedUser",
+			Protocol:      "NoAuthentication",
+		},
+	}
+
+	return c.CreateNamedCredential(ctx, namedCred)
+}
+
+func (c *Connector) createEventRelayConfing(
+	ctx context.Context,
+	params *RegistrationParams,
+	destinationResource string,
+	channelName string,
+) (*EventRelayConfig, error) {
+	config := &EventRelayConfig{
+		FullName: params.UniqueRef,
+		Metadata: &EventRelayConfigMetadata{
+			DestinationResourceName: destinationResource,
+			EventChannel:            channelName,
+		},
+	}
+
+	return c.CreateEventRelayConfig(ctx, config)
+}
+
+func IsCustomObject(objName string) bool {
+	return strings.HasSuffix(objName, "__c")
+}
+
+func GetRawObjectName(objName string) string {
+	return RemoveSuffix(objName, 3) //nolint:mnd
+}
+
+func GetChangeDataCaptureEventName(objName string) string {
+	if IsCustomObject(objName) {
+		return GetRawObjectName(objName) + "__ChangeEvent"
+	}
+
+	return objName + "%ChangeEvent"
+}
+
+func GetChannelName(rawChannelName string) string {
+	return rawChannelName + "__chn"
+}
+
+func RemoveSuffix(objName string, suffixLength int) string {
+	if len(objName) < suffixLength {
+		return ""
+	}
+
+	return objName[:len(objName)-suffixLength]
+}
+
+func GetRawChannelNameFromChannel(channelName string) string {
+	if strings.HasSuffix(channelName, "__chn") {
+		return RemoveSuffix(channelName, 5) //nolint:mnd
+	}
+
+	return channelName
+}
+
+func GetRawChannelNameFromObject(objectName string) string {
+	if strings.HasSuffix(objectName, "__e") {
+		return RemoveSuffix(objectName, 3) //nolint:mnd
+	}
+
+	return objectName
+}
+
+func GetChangeDataCaptureChannelMembershipName(rawChannelName string, eventName string) string {
+	return rawChannelName + "_chn_" + eventName
+}
+
+func GetRawPEName(peName string) string {
+	return RemoveSuffix(peName, 3) //nolint:mnd
+}
diff --git a/providers/salesforce/write.go b/providers/salesforce/write.go
index 891b9d88..ca056f5a 100644
--- a/providers/salesforce/write.go
+++ b/providers/salesforce/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -42,7 +42,7 @@ func parseWriteResult(rsp *common.JSONHTTPResponse) (*common.WriteResult, error)
 		}, nil
 	}
 
-	recordID, err := jsonquery.New(body).Str("id", false)
+	recordID, err := jsonquery.New(body).StringRequired("id")
 	if err != nil {
 		return nil, err
 	}
@@ -52,7 +52,7 @@ func parseWriteResult(rsp *common.JSONHTTPResponse) (*common.WriteResult, error)
 		return nil, err
 	}
 
-	success, err := jsonquery.New(body).Bool("success", false)
+	success, err := jsonquery.New(body).BoolRequired("success")
 	if err != nil {
 		return nil, err
 	}
@@ -60,15 +60,15 @@ func parseWriteResult(rsp *common.JSONHTTPResponse) (*common.WriteResult, error)
 	// Salesforce does not return record data upon successful write so we do not populate
 	// the corresponding result field
 	return &common.WriteResult{
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   errors,
-		Success:  *success,
+		Success:  success,
 	}, nil
 }
 
 // getErrors returns the errors from the response.
 func getErrors(node *ajson.Node) ([]any, error) {
-	arr, err := jsonquery.New(node).Array("errors", true)
+	arr, err := jsonquery.New(node).ArrayOptional("errors")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/salesloft/parse.go b/providers/salesloft/parse.go
index f7a49347..b65853b0 100644
--- a/providers/salesloft/parse.go
+++ b/providers/salesloft/parse.go
@@ -5,8 +5,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -31,7 +31,7 @@ Response example:
 	}
 */
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	arr, err := jsonquery.New(node).Array("data", false)
+	arr, err := jsonquery.New(node).ArrayRequired("data")
 	if err != nil {
 		return nil, err
 	}
@@ -41,7 +41,7 @@ func getRecords(node *ajson.Node) ([]map[string]any, error) {
 
 func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
-		nextPageNum, err := jsonquery.New(node, "metadata", "paging").Integer("next_page", true)
+		nextPageNum, err := jsonquery.New(node, "metadata", "paging").IntegerOptional("next_page")
 		if err != nil {
 			if errors.Is(err, jsonquery.ErrKeyNotFound) {
 				// list resource doesn't support pagination, hence no next page
diff --git a/providers/salesloft/write.go b/providers/salesloft/write.go
index e4e3fd97..2fc7324f 100644
--- a/providers/salesloft/write.go
+++ b/providers/salesloft/write.go
@@ -5,7 +5,7 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -49,12 +49,12 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(body *ajson.Node) (*common.WriteResult, error) {
-	nested, err := jsonquery.New(body).Object("data", false)
+	nested, err := jsonquery.New(body).ObjectRequired("data")
 	if err != nil {
 		return nil, err
 	}
 
-	rawID, err := jsonquery.New(nested).Integer("id", true)
+	rawID, err := jsonquery.New(nested).IntegerOptional("id")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/smartlead/parse.go b/providers/smartlead/parse.go
index 527484fe..00353fce 100644
--- a/providers/smartlead/parse.go
+++ b/providers/smartlead/parse.go
@@ -1,12 +1,12 @@
 package smartlead
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
 func getRecords(node *ajson.Node) ([]map[string]any, error) {
-	arr, err := jsonquery.New(node).Array("", false)
+	arr, err := jsonquery.New(node).ArrayRequired("")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/smartlead/write.go b/providers/smartlead/write.go
index affa2983..77f5c5ef 100644
--- a/providers/smartlead/write.go
+++ b/providers/smartlead/write.go
@@ -5,8 +5,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -89,7 +89,7 @@ func constructURLPathUpdate(config common.WriteParams, url *urlbuilder.URL) {
 
 func constructWriteResult(body *ajson.Node, recordIdLocation string) (*common.WriteResult, error) {
 	// ID is integer that is always stored under different field name.
-	rawID, err := jsonquery.New(body).Integer(recordIdLocation, true)
+	rawID, err := jsonquery.New(body).IntegerOptional(recordIdLocation)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/smartleadv2/handlers.go b/providers/smartleadv2/handlers.go
index 6f962df2..98cacf0f 100644
--- a/providers/smartleadv2/handlers.go
+++ b/providers/smartleadv2/handlers.go
@@ -9,8 +9,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"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) {
@@ -99,7 +99,7 @@ func (c *Connector) parseWriteResponse(
 	}
 
 	// ID is integer that is always stored under different field name.
-	rawID, err := jsonquery.New(node).Integer(idPath, true)
+	rawID, err := jsonquery.New(node).IntegerOptional(idPath)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/stripe/parse.go b/providers/stripe/parse.go
index ada7bffd..1ada459b 100644
--- a/providers/stripe/parse.go
+++ b/providers/stripe/parse.go
@@ -2,8 +2,8 @@ package stripe
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -25,7 +25,7 @@ func makeNextRecordsURL(url *urlbuilder.URL) common.NextPageFunc {
 			return "", nil
 		}
 
-		data, err := jsonquery.New(node).Array("data", true)
+		data, err := jsonquery.New(node).ArrayOptional("data")
 		if err != nil {
 			return "", err
 		}
@@ -36,7 +36,7 @@ func makeNextRecordsURL(url *urlbuilder.URL) common.NextPageFunc {
 
 		lastElement := data[len(data)-1]
 
-		lastItemID, err := jsonquery.New(lastElement).Str("id", true)
+		lastItemID, err := jsonquery.New(lastElement).StringOptional("id")
 		if err != nil {
 			return "", err
 		}
diff --git a/providers/stripe/write.go b/providers/stripe/write.go
index 8affa53f..c51c630f 100644
--- a/providers/stripe/write.go
+++ b/providers/stripe/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -54,7 +54,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(node *ajson.Node) (*common.WriteResult, error) {
-	recordID, err := jsonquery.New(node).Str("id", false)
+	recordID, err := jsonquery.New(node).StringRequired("id")
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +66,7 @@ func constructWriteResult(node *ajson.Node) (*common.WriteResult, error) {
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     data,
 	}, nil
diff --git a/providers/zendesksupport/objectNames.go b/providers/zendesksupport/objectNames.go
index 4722c8ea..650060d7 100644
--- a/providers/zendesksupport/objectNames.go
+++ b/providers/zendesksupport/objectNames.go
@@ -1,30 +1,6 @@
 package zendesksupport
 
-import (
-	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/internal/datautils"
-	"github.com/amp-labs/connectors/providers/zendesksupport/metadata"
-)
+import "github.com/amp-labs/connectors/providers/zendesksupport/metadata"
 
 // Supported object names can be found under schemas.json.
 var supportedObjectsByRead = metadata.Schemas.ObjectNames() //nolint:gochecknoglobals
-
-// ObjectNameToResponseField maps ObjectName to the response field name which contains that object.
-var ObjectNameToResponseField = common.ModuleObjectNameToFieldName{ //nolint:gochecknoglobals
-	ModuleTicketing: datautils.NewDefaultMap(map[string]string{
-		"ticket_audits":        "audits",
-		"search":               "results", // This is "/api/v2/search"
-		"satisfaction_reasons": "reasons",
-	},
-		func(objectName string) (fieldName string) {
-			return objectName
-		},
-	),
-	ModuleHelpCenter: datautils.NewDefaultMap(map[string]string{
-		"articles":        "results",
-		"article_labels":  "labels",
-		"community_posts": "results",
-	}, func(objectName string) (fieldName string) {
-		return objectName
-	}),
-}
diff --git a/providers/zendesksupport/parse.go b/providers/zendesksupport/parse.go
index b7c84798..8eab0529 100644
--- a/providers/zendesksupport/parse.go
+++ b/providers/zendesksupport/parse.go
@@ -1,7 +1,7 @@
 package zendesksupport
 
 import (
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/zendesksupport/read.go b/providers/zendesksupport/read.go
index b26d49a5..d8ec140e 100644
--- a/providers/zendesksupport/read.go
+++ b/providers/zendesksupport/read.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/providers/zendesksupport/metadata"
 )
 
 func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
@@ -26,7 +27,7 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		return nil, err
 	}
 
-	responseFieldName := ObjectNameToResponseField[c.Module.ID].Get(config.ObjectName)
+	responseFieldName := metadata.Schemas.LookupArrayFieldName(c.Module.ID, config.ObjectName)
 
 	return common.ParseResult(
 		rsp,
diff --git a/providers/zendesksupport/write.go b/providers/zendesksupport/write.go
index 0aceec78..cd58dfe0 100644
--- a/providers/zendesksupport/write.go
+++ b/providers/zendesksupport/write.go
@@ -5,8 +5,8 @@ import (
 	"strconv"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/naming"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -50,7 +50,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(config common.WriteParams, body *ajson.Node) (*common.WriteResult, error) {
-	nested, err := jsonquery.New(body).Object(config.ObjectName, true)
+	nested, err := jsonquery.New(body).ObjectOptional(config.ObjectName)
 	if err != nil {
 		return nil, err
 	}
@@ -58,9 +58,8 @@ func constructWriteResult(config common.WriteParams, body *ajson.Node) (*common.
 	if nested == nil {
 		// Field should be in singular form. Either one will be matched.
 		// This one is NOT optional.
-		nested, err = jsonquery.New(body).Object(
+		nested, err = jsonquery.New(body).ObjectOptional(
 			naming.NewSingularString(config.ObjectName).String(),
-			false,
 		)
 		if err != nil {
 			return nil, err
@@ -68,7 +67,7 @@ func constructWriteResult(config common.WriteParams, body *ajson.Node) (*common.
 	}
 	// nested node now must be not null, carry on
 
-	rawID, err := jsonquery.New(nested).Integer("id", true)
+	rawID, err := jsonquery.New(nested).IntegerOptional("id")
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/zohocrm/parse.go b/providers/zohocrm/parse.go
index 5d9e7afc..9e835f3f 100644
--- a/providers/zohocrm/parse.go
+++ b/providers/zohocrm/parse.go
@@ -2,8 +2,8 @@ package zohocrm
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -29,13 +29,13 @@ import (
 
 func getNextRecordsURL(url *urlbuilder.URL) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
-		hasMoreRecords, err := jsonquery.New(node, "info").Bool("more_records", false)
+		hasMoreRecords, err := jsonquery.New(node, "info").BoolRequired("more_records")
 		if err != nil {
 			return "", err
 		}
 
-		if *hasMoreRecords {
-			pageToken, err := jsonquery.New(node, "info").Str("next_page_token", true)
+		if hasMoreRecords {
+			pageToken, err := jsonquery.New(node, "info").StringOptional("next_page_token")
 			if err != nil {
 				return "", err
 			}
diff --git a/providers/zoom/parse.go b/providers/zoom/parse.go
index a409c6d3..ef86b4ae 100644
--- a/providers/zoom/parse.go
+++ b/providers/zoom/parse.go
@@ -2,8 +2,8 @@ package zoom
 
 import (
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
diff --git a/providers/zoom/write.go b/providers/zoom/write.go
index c7cf2a66..3c0291af 100644
--- a/providers/zoom/write.go
+++ b/providers/zoom/write.go
@@ -4,7 +4,7 @@ import (
 	"context"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/spyzhov/ajson"
 )
 
@@ -53,7 +53,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 }
 
 func constructWriteResult(body *ajson.Node, recordIdLocation string) (*common.WriteResult, error) {
-	recordID, err := jsonquery.New(body).Str(recordIdLocation, false)
+	recordID, err := jsonquery.New(body).StringRequired(recordIdLocation)
 	if err != nil {
 		return nil, err
 	}
@@ -65,7 +65,7 @@ func constructWriteResult(body *ajson.Node, recordIdLocation string) (*common.Wr
 
 	return &common.WriteResult{
 		Success:  true,
-		RecordId: *recordID,
+		RecordId: recordID,
 		Errors:   nil,
 		Data:     data,
 	}, nil
diff --git a/scripts/oauth/token.go b/scripts/oauth/token.go
index 36129280..b97b50c6 100644
--- a/scripts/oauth/token.go
+++ b/scripts/oauth/token.go
@@ -23,6 +23,7 @@ import (
 	"github.com/amp-labs/connectors/common/scanning/credscanning"
 	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/providers"
+	"github.com/amp-labs/connectors/scripts/utils/credutils"
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/clientcredentials"
 )
@@ -101,6 +102,7 @@ type OAuthApp struct {
 	SSLKey          string
 	CodeVerifier    *string
 	PasswordParams  *providers.BasicParams
+	WriteCreds      bool
 }
 
 // ServeHTTP implements the http.Handler interface.
@@ -169,6 +171,13 @@ func (a *OAuthApp) processCallback(writer http.ResponseWriter, request *http.Req
 	// Print the token which will also print raw metadata
 	fmt.Printf("%+v", tok)
 
+	if a.WriteCreds {
+		// Update the creds.json file with the new token values.
+		if err = credutils.WriteToken(DefaultCredsFile, tok); err != nil {
+			slog.Warn("Couldn't write token information to creds file", "error", err)
+		}
+	}
+
 	writer.Header().Set("Content-Type", "application/json")
 	writer.Header().Set("Content-Length", strconv.FormatInt(int64(len(jsonBody)), 10))
 	writer.WriteHeader(http.StatusOK)
@@ -269,6 +278,9 @@ func setup() *OAuthApp {
 	proto := flag.String("proto", HttpProtocol, "http or https protocol")
 
 	callback := flag.String("callback", DefaultCallbackPath, "the full OAuth callback path (arbitrary)")
+	writeCreds := flag.Bool("writeCreds", false,
+		"Enable updating creds.json and <provider>-creds.json files")
+
 	flag.Parse()
 
 	if err := registry.AddReaders(readers...); err != nil {
@@ -351,6 +363,7 @@ func setup() *OAuthApp {
 			},
 			AuthOptions:     make([]oauth2.AuthCodeOption, 0),
 			ExchangeOptions: make([]oauth2.AuthCodeOption, 0),
+			WriteCreds:      *writeCreds,
 		}
 		if state != "" {
 			app.State = state
diff --git a/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go b/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go
index ece8abf1..23871132 100644
--- a/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go
+++ b/scripts/openapi/zendesksupport/metadata/helpcenter/objects.go
@@ -1,9 +1,9 @@
 package helpcenter
 
 import (
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/metadatadef"
-	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/providers/zendesksupport/openapi"
 	"github.com/amp-labs/connectors/tools/fileconv/api3"
 )
@@ -18,6 +18,13 @@ var (
 		"/api/v2/help_center/articles/labels":        "article_labels",
 		"/api/v2/help_center/community_posts/search": "community_posts",
 	}
+	objectNameToResponseField = datautils.NewDefaultMap(map[string]string{ // nolint:gochecknoglobals
+		"articles":        "results",
+		"article_labels":  "labels",
+		"community_posts": "results",
+	}, func(objectName string) (fieldName string) {
+		return objectName
+	})
 )
 
 func Objects() []metadatadef.Schema {
@@ -32,7 +39,7 @@ func Objects() []metadatadef.Schema {
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		objectEndpoints, nil,
-		api3.CustomMappingObjectCheck(zendesksupport.ObjectNameToResponseField[zendesksupport.ModuleHelpCenter]),
+		api3.CustomMappingObjectCheck(objectNameToResponseField),
 	)
 	goutils.MustBeNil(err)
 
diff --git a/scripts/openapi/zendesksupport/metadata/support/objects.go b/scripts/openapi/zendesksupport/metadata/support/objects.go
index abf3270f..142428f2 100644
--- a/scripts/openapi/zendesksupport/metadata/support/objects.go
+++ b/scripts/openapi/zendesksupport/metadata/support/objects.go
@@ -1,9 +1,9 @@
 package support
 
 import (
+	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/metadatadef"
-	"github.com/amp-labs/connectors/providers/zendesksupport"
 	"github.com/amp-labs/connectors/providers/zendesksupport/openapi"
 	"github.com/amp-labs/connectors/tools/fileconv/api3"
 )
@@ -66,6 +66,13 @@ var (
 		"satisfaction_reasons": "Satisfaction Rating Reasons",
 		"ticket_audits":        "Ticket Audits",
 	}
+	objectNameToResponseField = datautils.NewDefaultMap(map[string]string{ // nolint:gochecknoglobals
+		"ticket_audits":        "audits",
+		"search":               "results", // This is "/api/v2/search"
+		"satisfaction_reasons": "reasons",
+	}, func(objectName string) (fieldName string) {
+		return objectName
+	})
 )
 
 func Objects() []metadatadef.Schema {
@@ -80,7 +87,7 @@ func Objects() []metadatadef.Schema {
 	objects, err := explorer.ReadObjectsGet(
 		api3.NewDenyPathStrategy(ignoreEndpoints),
 		nil, displayNameOverride,
-		api3.CustomMappingObjectCheck(zendesksupport.ObjectNameToResponseField[zendesksupport.ModuleTicketing]),
+		api3.CustomMappingObjectCheck(objectNameToResponseField),
 	)
 	goutils.MustBeNil(err)
 
diff --git a/scripts/utils/credutils/tokenWriter.go b/scripts/utils/credutils/tokenWriter.go
new file mode 100644
index 00000000..2328ddb7
--- /dev/null
+++ b/scripts/utils/credutils/tokenWriter.go
@@ -0,0 +1,66 @@
+package credutils
+
+import (
+	"bufio"
+	"encoding/json"
+	"errors"
+	"io"
+	"os"
+	"time"
+
+	"github.com/amp-labs/connectors/common/scanning/credscanning"
+	"gitlab.com/c0b/go-ordered-json"
+	"golang.org/x/oauth2"
+)
+
+func WriteToken(defaultCredsFilePath string, token *oauth2.Token) error {
+	credentials, err := internalCredsFileWrite(defaultCredsFilePath, token)
+	if err != nil {
+		return err
+	}
+
+	// Create/Update provider prefixed file.
+	// Ex: keap-creds.json
+	provider := credentials.Get(credscanning.Fields.Provider.PathJSON)
+
+	providerName, ok := provider.(string)
+	if !ok {
+		return errors.New("cannot infer provider in credentials file") // nolint:err113
+	}
+
+	providerCredsFilePath := credscanning.LoadPath(providerName)
+	_, err = internalCredsFileWrite(providerCredsFilePath, token)
+
+	return err
+}
+
+func internalCredsFileWrite(filePath string, token *oauth2.Token) (*ordered.OrderedMap, error) {
+	jsonFile, err := os.Open(filePath)
+	if err != nil {
+		return nil, err
+	}
+
+	reader := bufio.NewReader(jsonFile)
+
+	fileData, err := io.ReadAll(reader)
+	if err != nil {
+		return nil, err
+	}
+
+	orderedMap := ordered.NewOrderedMap()
+	if err = orderedMap.UnmarshalJSON(fileData); err != nil {
+		return nil, err
+	}
+
+	orderedMap.Set(credscanning.Fields.AccessToken.PathJSON, token.AccessToken)
+	orderedMap.Set(credscanning.Fields.RefreshToken.PathJSON, token.RefreshToken)
+	orderedMap.Set(credscanning.Fields.ExpiryFormat.PathJSON, "RFC3339Nano")
+	orderedMap.Set(credscanning.Fields.Expiry.PathJSON, token.Expiry.UTC().Format(time.RFC3339Nano))
+
+	outputData, err := json.MarshalIndent(orderedMap, "", "  ")
+	if err != nil {
+		return nil, err
+	}
+
+	return orderedMap, os.WriteFile(filePath, outputData, os.ModePerm) // nolint:gosec
+}


Test patch:
diff --git a/common/jsonquery/queries_test.go b/common/jsonquery/queries_test.go
deleted file mode 100644
index 92d0c19b..00000000
--- a/common/jsonquery/queries_test.go
+++ /dev/null
@@ -1,517 +0,0 @@
-package jsonquery
-
-import (
-	"errors"
-	"reflect"
-	"testing"
-
-	"github.com/amp-labs/connectors/internal/goutils"
-	"github.com/amp-labs/connectors/test/utils/testutils"
-	"github.com/spyzhov/ajson"
-)
-
-// nolint:gochecknoglobals
-var testJSONData = `{
-		"count":38, "text":"Hello World", "pi":3.14, "metadata":null, "list":[1,2,3], "arr":[],
-		"inProgress": false,
-		"payload": {
-			"notes": {
-				"links": null,
-				"body": {
-					"text": "Some notes",
-					"amount": 359,
-					"purchased": true
-				},
-				"nested_arr": [1,2,3,4,5]
-			},
-			"display_time": "yesterday"
-		}}`
-
-func TestQueryInteger(t *testing.T) { // nolint:funlen
-	t.Parallel()
-
-	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
-
-	type inType struct {
-		key      string
-		optional bool
-		zoom     []string
-	}
-
-	tests := []struct {
-		name        string
-		input       inType
-		expected    *int64
-		expectedErr error
-	}{
-		{
-			name: "Missing required integer",
-			input: inType{
-				key:      "age",
-				optional: false,
-			},
-			expectedErr: ErrKeyNotFound,
-		},
-		{
-			name: "Missing optional integer",
-			input: inType{
-				key:      "age",
-				optional: true,
-			},
-			expected: nil,
-		},
-		{
-			name: "Incorrect data type",
-			input: inType{
-				key: "text",
-			},
-			expectedErr: ErrNotNumeric,
-		},
-		{
-			name: "Float is not integer",
-			input: inType{
-				key: "pi",
-			},
-			expectedErr: ErrNotInteger,
-		},
-		{
-			name: "Key is found but Null",
-			input: inType{
-				key: "metadata",
-			},
-			expectedErr: ErrNullJSON,
-		},
-		{
-			name: "Valid number",
-			input: inType{
-				key: "count",
-			},
-			expected: goutils.Pointer[int64](38),
-		},
-		{
-			name: "Reaching for nested integer",
-			input: inType{
-				key:      "amount",
-				optional: false,
-				zoom:     []string{"payload", "notes", "body"},
-			},
-			expected: goutils.Pointer[int64](359),
-		},
-		{
-			name: "Reaching for nested integer using self reference",
-			input: inType{
-				key:      "", // empty string acts as 'self'
-				optional: false,
-				zoom:     []string{"payload", "notes", "body", "amount"},
-			},
-			expected: goutils.Pointer[int64](359),
-		},
-		{
-			name: "Non existent zoom path is ok for optional integer",
-			input: inType{
-				key:      "street",
-				optional: true,
-				zoom:     []string{"payload", "location", "address"},
-			},
-			expected: nil,
-		},
-	}
-
-	for _, tt := range tests {
-		// nolint:varnamelen
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
-			output, err := New(j, tt.input.zoom...).Integer(tt.input.key, tt.input.optional)
-			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
-		})
-	}
-}
-
-func TestQueryString(t *testing.T) { // nolint:funlen
-	t.Parallel()
-
-	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
-
-	type inType struct {
-		key      string
-		optional bool
-		zoom     []string
-	}
-
-	tests := []struct {
-		name        string
-		input       inType
-		expected    *string
-		expectedErr error
-	}{
-		{
-			name: "Missing required string",
-			input: inType{
-				key:      "surname",
-				optional: false,
-			},
-			expectedErr: ErrKeyNotFound,
-		},
-		{
-			name: "Missing optional string",
-			input: inType{
-				key:      "surname",
-				optional: true,
-			},
-			expected: nil,
-		},
-		{
-			name: "Incorrect data type",
-			input: inType{
-				key: "count",
-			},
-			expectedErr: ErrNotString,
-		},
-		{
-			name: "Key is found but Null",
-			input: inType{
-				key: "metadata",
-			},
-			expectedErr: ErrNullJSON,
-		},
-		{
-			name: "Valid string",
-			input: inType{
-				key: "text",
-			},
-			expected: goutils.Pointer("Hello World"),
-		},
-		{
-			name: "Reaching for nested string",
-			input: inType{
-				key:      "text",
-				optional: false,
-				zoom:     []string{"payload", "notes", "body"},
-			},
-			expected: goutils.Pointer("Some notes"),
-		},
-		{
-			name: "Reaching for nested string using self reference",
-			input: inType{
-				key:      "", // empty string acts as 'self'
-				optional: false,
-				zoom:     []string{"payload", "notes", "body", "text"},
-			},
-			expected: goutils.Pointer("Some notes"),
-		},
-		{
-			name: "Non existent zoom path is ok for optional string",
-			input: inType{
-				key:      "street",
-				optional: true,
-				zoom:     []string{"payload", "location", "address"},
-			},
-			expected: nil,
-		},
-	}
-
-	for _, tt := range tests {
-		// nolint:varnamelen
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
-			output, err := New(j, tt.input.zoom...).Str(tt.input.key, tt.input.optional)
-			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
-		})
-	}
-}
-
-func TestQueryBool(t *testing.T) { // nolint:funlen
-	t.Parallel()
-
-	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
-
-	type inType struct {
-		key      string
-		optional bool
-		zoom     []string
-	}
-
-	tests := []struct {
-		name        string
-		input       inType
-		expected    *bool
-		expectedErr error
-	}{
-		{
-			name: "Missing required bool",
-			input: inType{
-				key:      "completed",
-				optional: false,
-			},
-			expectedErr: ErrKeyNotFound,
-		},
-		{
-			name: "Missing optional bool",
-			input: inType{
-				key:      "completed",
-				optional: true,
-			},
-			expected: nil,
-		},
-		{
-			name: "Incorrect data type",
-			input: inType{
-				key: "count",
-			},
-			expectedErr: ErrNotBool,
-		},
-		{
-			name: "Key is found but Null",
-			input: inType{
-				key: "metadata",
-			},
-			expectedErr: ErrNullJSON,
-		},
-		{
-			name: "Valid Bool",
-			input: inType{
-				key: "inProgress",
-			},
-			expected: goutils.Pointer(false),
-		},
-		{
-			name: "Reaching for nested bool",
-			input: inType{
-				key:      "purchased",
-				optional: false,
-				zoom:     []string{"payload", "notes", "body"},
-			},
-			expected: goutils.Pointer(true),
-		},
-		{
-			name: "Reaching for nested bool using self reference",
-			input: inType{
-				key:      "", // empty string acts as 'self'
-				optional: false,
-				zoom:     []string{"payload", "notes", "body", "purchased"},
-			},
-			expected: goutils.Pointer(true),
-		},
-		{
-			name: "Non existent zoom path is ok for optional bool",
-			input: inType{
-				key:      "applicable",
-				optional: true,
-				zoom:     []string{"payload", "location", "address"},
-			},
-			expected: nil,
-		},
-	}
-
-	for _, tt := range tests {
-		// nolint:varnamelen
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
-			output, err := New(j, tt.input.zoom...).Bool(tt.input.key, tt.input.optional)
-			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
-		})
-	}
-}
-
-func TestQueryArray(t *testing.T) { // nolint:funlen
-	t.Parallel()
-
-	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
-
-	type inType struct {
-		key      string
-		optional bool
-		zoom     []string
-	}
-
-	tests := []struct {
-		name         string
-		input        inType
-		expectedSize int
-		expectedErr  error
-	}{
-		{
-			name: "Missing required array",
-			input: inType{
-				key:      "queue",
-				optional: false,
-			},
-			expectedSize: 0,
-			expectedErr:  ErrKeyNotFound,
-		},
-		{
-			name: "Key is found but Null",
-			input: inType{
-				key:      "metadata",
-				optional: false,
-			},
-			expectedErr: ErrNullJSON,
-		},
-		{
-			name: "Value with wrong type",
-			input: inType{
-				key:      "text",
-				optional: false,
-			},
-			expectedErr: ErrNotArray,
-		},
-		{
-			name: "Valid array",
-			input: inType{
-				key:      "list",
-				optional: false,
-			},
-			expectedSize: 3,
-		},
-		{
-			name: "Empty array",
-			input: inType{
-				key:      "arr",
-				optional: false,
-			},
-			expectedSize: 0,
-			expectedErr:  nil,
-		},
-		{
-			name: "Reaching for nested array",
-			input: inType{
-				key:      "nested_arr",
-				optional: false,
-				zoom:     []string{"payload", "notes"},
-			},
-			expectedSize: 5,
-		},
-		{
-			name: "Reaching for nested array using self reference",
-			input: inType{
-				key:      "", // empty string acts as 'self'
-				optional: false,
-				zoom:     []string{"payload", "notes", "nested_arr"},
-			},
-			expectedSize: 5,
-		},
-		{
-			name: "Non existent zoom path is ok for optional arr",
-			input: inType{
-				key:      "street",
-				optional: true,
-				zoom:     []string{"payload", "location", "address"},
-			},
-			expectedSize: 0,
-			expectedErr:  nil,
-		},
-	}
-
-	for _, tt := range tests { // nolint:varnamelen
-		// nolint:varnamelen
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
-			output, err := New(j, tt.input.zoom...).Array(tt.input.key, tt.input.optional)
-
-			if !errors.Is(err, tt.expectedErr) {
-				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedErr, err)
-			}
-
-			outputSize := len(output)
-			if !reflect.DeepEqual(outputSize, tt.expectedSize) {
-				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedSize, outputSize)
-			}
-		})
-	}
-}
-
-func TestQueryObject(t *testing.T) { // nolint:funlen
-	t.Parallel()
-
-	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
-
-	type inType struct {
-		key      string
-		optional bool
-		zoom     []string
-	}
-
-	tests := []struct {
-		name        string
-		input       inType
-		expectedErr error
-	}{
-		{
-			name: "Missing required nested object",
-			input: inType{
-				key:  "random",
-				zoom: []string{"payload"},
-			},
-			expectedErr: ErrKeyNotFound,
-		},
-		{
-			name: "Nested key is found but Null",
-			input: inType{
-				key:  "links",
-				zoom: []string{"payload", "notes"},
-			},
-			expectedErr: ErrNullJSON,
-		},
-		{
-			name: "Invalid data type of existing key",
-			input: inType{
-				key:  "text",
-				zoom: []string{"payload", "notes", "body"},
-			},
-			expectedErr: ErrNotObject,
-		},
-		{
-			name: "Valid nested object",
-			input: inType{
-				key:  "body",
-				zoom: []string{"payload", "notes"},
-			},
-			expectedErr: nil, // success
-		},
-		{
-			name: "Valid nested object using self reference",
-			input: inType{
-				key:  "", // empty string acts as 'self'
-				zoom: []string{"payload", "notes", "body"},
-			},
-			expectedErr: nil, // success
-		},
-		{
-			name: "Non existent zoom path is ok for optional object",
-			input: inType{
-				key:      "street",
-				optional: true,
-				zoom:     []string{"payload", "location", "address"},
-			},
-			expectedErr: nil, // success
-		},
-	}
-
-	for _, tt := range tests {
-		// nolint:varnamelen
-		t.Run(tt.name, func(t *testing.T) {
-			t.Parallel()
-
-			_, err := New(j, tt.input.zoom...).Object(tt.input.key, tt.input.optional)
-
-			if !errors.Is(err, tt.expectedErr) {
-				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedErr, err)
-			}
-		})
-	}
-}
-
-func helperCreateJSON(t *testing.T, text string) *ajson.Node {
-	t.Helper()
-
-	jsonBody, err := ajson.Unmarshal([]byte(text))
-	if err != nil {
-		t.Fatalf("bad test, JSON object is invalid, cannot proceed with the test")
-	}
-
-	return jsonBody
-}
diff --git a/internal/jsonquery/queries_test.go b/internal/jsonquery/queries_test.go
new file mode 100644
index 00000000..b55558d5
--- /dev/null
+++ b/internal/jsonquery/queries_test.go
@@ -0,0 +1,910 @@
+// nolint:dupl
+package jsonquery
+
+import (
+	"errors"
+	"reflect"
+	"testing"
+
+	"github.com/amp-labs/connectors/internal/goutils"
+	"github.com/amp-labs/connectors/test/utils/testutils"
+	"github.com/spyzhov/ajson"
+)
+
+// nolint:gochecknoglobals
+var testJSONData = `{
+		"count":38, "text":"Hello World", "pi":3.14, "metadata":null, "list":[1,2,3], "arr":[],
+		"inProgress": false,
+		"payload": {
+			"notes": {
+				"links": null,
+				"body": {
+					"text": "Some notes",
+					"amount": 359,
+					"purchased": true
+				},
+				"nested_arr": [1,2,3,4,5]
+			},
+			"display_time": "yesterday"
+		}}`
+
+func TestQueryIntegerOptional(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expected    *int64
+		expectedErr error
+	}{
+		{
+			name: "Missing optional integer",
+			input: inType{
+				key: "age",
+			},
+			expected:    nil,
+			expectedErr: nil,
+		},
+		{
+			name: "Incorrect data type",
+			input: inType{
+				key: "text",
+			},
+			expectedErr: ErrNotNumeric,
+		},
+		{
+			name: "Float is not integer",
+			input: inType{
+				key: "pi",
+			},
+			expectedErr: ErrNotInteger,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expected:    nil,
+			expectedErr: nil,
+		},
+		{
+			name: "Valid number",
+			input: inType{
+				key: "count",
+			},
+			expected: goutils.Pointer[int64](38),
+		},
+		{
+			name: "Reaching for nested integer",
+			input: inType{
+				key:  "amount",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expected: goutils.Pointer[int64](359),
+		},
+		{
+			name: "Reaching for nested integer using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body", "amount"},
+			},
+			expected: goutils.Pointer[int64](359),
+		},
+		{
+			name: "Non existent zoom path is ok for optional integer",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expected: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).IntegerOptional(tt.input.key)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
+		})
+	}
+}
+
+func TestQueryIntegerRequired(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expected    int64
+		expectedErr error
+	}{
+		{
+			name: "Missing required integer",
+			input: inType{
+				key: "age",
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+		{
+			name: "Incorrect data type",
+			input: inType{
+				key: "text",
+			},
+			expectedErr: ErrNotNumeric,
+		},
+		{
+			name: "Float is not integer",
+			input: inType{
+				key: "pi",
+			},
+			expectedErr: ErrNotInteger,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expectedErr: ErrNullJSON,
+		},
+		{
+			name: "Valid number",
+			input: inType{
+				key: "count",
+			},
+			expected: 38,
+		},
+		{
+			name: "Reaching for nested integer",
+			input: inType{
+				key:  "amount",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expected: 359,
+		},
+		{
+			name: "Reaching for nested integer using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body", "amount"},
+			},
+			expected: 359,
+		},
+		{
+			name: "Non existent zoom path throws key not found",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).IntegerRequired(tt.input.key)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
+		})
+	}
+}
+
+func TestQueryStringOptional(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expected    *string
+		expectedErr error
+	}{
+		{
+			name: "Missing optional string",
+			input: inType{
+				key: "surname",
+			},
+			expected:    nil,
+			expectedErr: nil,
+		},
+		{
+			name: "Incorrect data type",
+			input: inType{
+				key: "count",
+			},
+			expectedErr: ErrNotString,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expected:    nil,
+			expectedErr: nil,
+		},
+		{
+			name: "Valid string",
+			input: inType{
+				key: "text",
+			},
+			expected: goutils.Pointer("Hello World"),
+		},
+		{
+			name: "Reaching for nested string",
+			input: inType{
+				key:  "text",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expected: goutils.Pointer("Some notes"),
+		},
+		{
+			name: "Reaching for nested string using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body", "text"},
+			},
+			expected: goutils.Pointer("Some notes"),
+		},
+		{
+			name: "Non existent zoom path is ok for optional string",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expected: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).StringOptional(tt.input.key)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
+		})
+	}
+}
+
+func TestQueryStringRequired(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expected    string
+		expectedErr error
+	}{
+		{
+			name: "Missing required string",
+			input: inType{
+				key: "surname",
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+		{
+			name: "Incorrect data type",
+			input: inType{
+				key: "count",
+			},
+			expectedErr: ErrNotString,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expectedErr: ErrNullJSON,
+		},
+		{
+			name: "Valid string",
+			input: inType{
+				key: "text",
+			},
+			expected: "Hello World",
+		},
+		{
+			name: "Reaching for nested string",
+			input: inType{
+				key:  "text",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expected: "Some notes",
+		},
+		{
+			name: "Reaching for nested string using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body", "text"},
+			},
+			expected: "Some notes",
+		},
+		{
+			name: "Non existent zoom path throws key not found",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).StringRequired(tt.input.key)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
+		})
+	}
+}
+
+func TestQueryBoolOptional(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expected    *bool
+		expectedErr error
+	}{
+		{
+			name: "Missing optional bool",
+			input: inType{
+				key: "completed",
+			},
+			expected:    nil,
+			expectedErr: nil,
+		},
+		{
+			name: "Incorrect data type",
+			input: inType{
+				key: "count",
+			},
+			expectedErr: ErrNotBool,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expected:    nil,
+			expectedErr: nil,
+		},
+		{
+			name: "Valid Bool",
+			input: inType{
+				key: "inProgress",
+			},
+			expected: goutils.Pointer(false),
+		},
+		{
+			name: "Reaching for nested bool",
+			input: inType{
+				key:  "purchased",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expected: goutils.Pointer(true),
+		},
+		{
+			name: "Reaching for nested bool using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body", "purchased"},
+			},
+			expected: goutils.Pointer(true),
+		},
+		{
+			name: "Non existent zoom path is ok for optional bool",
+			input: inType{
+				key:  "applicable",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expected: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).BoolOptional(tt.input.key)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
+		})
+	}
+}
+
+func TestQueryBoolRequired(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expected    bool
+		expectedErr error
+	}{
+		{
+			name: "Missing required bool",
+			input: inType{
+				key: "completed",
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+		{
+			name: "Incorrect data type",
+			input: inType{
+				key: "count",
+			},
+			expectedErr: ErrNotBool,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expectedErr: ErrNullJSON,
+		},
+		{
+			name: "Valid Bool",
+			input: inType{
+				key: "inProgress",
+			},
+			expected: false,
+		},
+		{
+			name: "Reaching for nested bool",
+			input: inType{
+				key:  "purchased",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expected: true,
+		},
+		{
+			name: "Reaching for nested bool using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body", "purchased"},
+			},
+			expected: true,
+		},
+		{
+			name: "Non existent zoom path throws key not found",
+			input: inType{
+				key:  "applicable",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).BoolRequired(tt.input.key)
+			testutils.CheckOutputWithError(t, tt.name, tt.expected, tt.expectedErr, output, err)
+		})
+	}
+}
+
+func TestQueryArrayOptional(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name         string
+		input        inType
+		expectedSize int
+		expectedErr  error
+	}{
+		{
+			name: "Missing optional array",
+			input: inType{
+				key: "queue",
+			},
+			expectedSize: 0, // empty array, there is nothing
+			expectedErr:  nil,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key: "metadata",
+			},
+			expectedSize: 0, // empty array, there is nothing
+		},
+		{
+			name: "Value with wrong type",
+			input: inType{
+				key: "text",
+			},
+			expectedErr: ErrNotArray,
+		},
+		{
+			name: "Valid array",
+			input: inType{
+				key: "list",
+			},
+			expectedSize: 3,
+		},
+		{
+			name: "Empty array",
+			input: inType{
+				key: "arr",
+			},
+			expectedSize: 0,
+			expectedErr:  nil,
+		},
+		{
+			name: "Reaching for nested array",
+			input: inType{
+				key:  "nested_arr",
+				zoom: []string{"payload", "notes"},
+			},
+			expectedSize: 5,
+		},
+		{
+			name: "Reaching for nested array using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "nested_arr"},
+			},
+			expectedSize: 5,
+		},
+		{
+			name: "Non existent zoom path is ok for optional arr",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedSize: 0,
+			expectedErr:  nil,
+		},
+	}
+
+	for _, tt := range tests { // nolint:varnamelen
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).ArrayOptional(tt.input.key)
+
+			if !errors.Is(err, tt.expectedErr) {
+				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedErr, err)
+			}
+
+			outputSize := len(output)
+			if !reflect.DeepEqual(outputSize, tt.expectedSize) {
+				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedSize, outputSize)
+			}
+		})
+	}
+}
+
+func TestQueryArrayRequired(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key      string
+		optional bool
+		zoom     []string
+	}
+
+	tests := []struct {
+		name         string
+		input        inType
+		expectedSize int
+		expectedErr  error
+	}{
+		{
+			name: "Missing required array",
+			input: inType{
+				key:      "queue",
+				optional: false,
+			},
+			expectedSize: 0,
+			expectedErr:  ErrKeyNotFound,
+		},
+		{
+			name: "Key is found but Null",
+			input: inType{
+				key:      "metadata",
+				optional: false,
+			},
+			expectedErr: ErrNullJSON,
+		},
+		{
+			name: "Value with wrong type",
+			input: inType{
+				key:      "text",
+				optional: false,
+			},
+			expectedErr: ErrNotArray,
+		},
+		{
+			name: "Valid array",
+			input: inType{
+				key:      "list",
+				optional: false,
+			},
+			expectedSize: 3,
+		},
+		{
+			name: "Empty array",
+			input: inType{
+				key:      "arr",
+				optional: false,
+			},
+			expectedSize: 0,
+			expectedErr:  nil,
+		},
+		{
+			name: "Reaching for nested array",
+			input: inType{
+				key:      "nested_arr",
+				optional: false,
+				zoom:     []string{"payload", "notes"},
+			},
+			expectedSize: 5,
+		},
+		{
+			name: "Reaching for nested array using self reference",
+			input: inType{
+				key:      "", // empty string acts as 'self'
+				optional: false,
+				zoom:     []string{"payload", "notes", "nested_arr"},
+			},
+			expectedSize: 5,
+		},
+		{
+			name: "Non existent zoom throws key not found",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedSize: 0,
+			expectedErr:  ErrKeyNotFound,
+		},
+	}
+
+	for _, tt := range tests { // nolint:varnamelen
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			output, err := New(j, tt.input.zoom...).ArrayRequired(tt.input.key)
+
+			if !errors.Is(err, tt.expectedErr) {
+				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedErr, err)
+			}
+
+			outputSize := len(output)
+			if !reflect.DeepEqual(outputSize, tt.expectedSize) {
+				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedSize, outputSize)
+			}
+		})
+	}
+}
+
+func TestQueryObjectOptional(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expectedErr error
+	}{
+		{
+			name: "Missing optional nested object",
+			input: inType{
+				key:  "random",
+				zoom: []string{"payload"},
+			},
+			expectedErr: nil, // success
+		},
+		{
+			name: "Nested key is found but Null",
+			input: inType{
+				key:  "links",
+				zoom: []string{"payload", "notes"},
+			},
+			expectedErr: nil, // success
+		},
+		{
+			name: "Invalid data type of existing key",
+			input: inType{
+				key:  "text",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expectedErr: ErrNotObject,
+		},
+		{
+			name: "Valid nested object",
+			input: inType{
+				key:  "body",
+				zoom: []string{"payload", "notes"},
+			},
+			expectedErr: nil, // success
+		},
+		{
+			name: "Valid nested object using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expectedErr: nil, // success
+		},
+		{
+			name: "Non existent zoom path is ok for optional object",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedErr: nil, // success
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			_, err := New(j, tt.input.zoom...).ObjectOptional(tt.input.key)
+
+			if !errors.Is(err, tt.expectedErr) {
+				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedErr, err)
+			}
+		})
+	}
+}
+
+func TestQueryObjectRequired(t *testing.T) { // nolint:funlen
+	t.Parallel()
+
+	j := helperCreateJSON(t, testJSONData) // nolint:varnamelen
+
+	type inType struct {
+		key  string
+		zoom []string
+	}
+
+	tests := []struct {
+		name        string
+		input       inType
+		expectedErr error
+	}{
+		{
+			name: "Missing required nested object",
+			input: inType{
+				key:  "random",
+				zoom: []string{"payload"},
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+		{
+			name: "Nested key is found but Null",
+			input: inType{
+				key:  "links",
+				zoom: []string{"payload", "notes"},
+			},
+			expectedErr: ErrNullJSON,
+		},
+		{
+			name: "Invalid data type of existing key",
+			input: inType{
+				key:  "text",
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expectedErr: ErrNotObject,
+		},
+		{
+			name: "Valid nested object",
+			input: inType{
+				key:  "body",
+				zoom: []string{"payload", "notes"},
+			},
+			expectedErr: nil, // success
+		},
+		{
+			name: "Valid nested object using self reference",
+			input: inType{
+				key:  "", // empty string acts as 'self'
+				zoom: []string{"payload", "notes", "body"},
+			},
+			expectedErr: nil, // success
+		},
+		{
+			name: "Non existent zoom path throws key not found",
+			input: inType{
+				key:  "street",
+				zoom: []string{"payload", "location", "address"},
+			},
+			expectedErr: ErrKeyNotFound,
+		},
+	}
+
+	for _, tt := range tests {
+		// nolint:varnamelen
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			_, err := New(j, tt.input.zoom...).ObjectRequired(tt.input.key)
+
+			if !errors.Is(err, tt.expectedErr) {
+				t.Fatalf("%s: expected: (%v), got: (%v)", tt.name, tt.expectedErr, err)
+			}
+		})
+	}
+}
+
+func helperCreateJSON(t *testing.T, text string) *ajson.Node {
+	t.Helper()
+
+	jsonBody, err := ajson.Unmarshal([]byte(text))
+	if err != nil {
+		t.Fatalf("bad test, JSON object is invalid, cannot proceed with the test")
+	}
+
+	return jsonBody
+}
diff --git a/providers/atlassian/read_test.go b/providers/atlassian/read_test.go
index 18a1d398..5c060cf4 100644
--- a/providers/atlassian/read_test.go
+++ b/providers/atlassian/read_test.go
@@ -9,7 +9,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/dynamicscrm/read_test.go b/providers/dynamicscrm/read_test.go
index 4642af24..4e0498ac 100644
--- a/providers/dynamicscrm/read_test.go
+++ b/providers/dynamicscrm/read_test.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/gong/read_test.go b/providers/gong/read_test.go
index 15954fcb..82942135 100644
--- a/providers/gong/read_test.go
+++ b/providers/gong/read_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/instantly/read_test.go b/providers/instantly/read_test.go
index 08c09a58..c6a65dc4 100644
--- a/providers/instantly/read_test.go
+++ b/providers/instantly/read_test.go
@@ -9,7 +9,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/intercom/read_test.go b/providers/intercom/read_test.go
index 0bf9babd..089a0590 100644
--- a/providers/intercom/read_test.go
+++ b/providers/intercom/read_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/pipeliner/read_test.go b/providers/pipeliner/read_test.go
index 32db71b1..8fb00f3f 100644
--- a/providers/pipeliner/read_test.go
+++ b/providers/pipeliner/read_test.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/salesforce/read_test.go b/providers/salesforce/read_test.go
index 39f39637..848d1f95 100644
--- a/providers/salesforce/read_test.go
+++ b/providers/salesforce/read_test.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/salesloft/read_test.go b/providers/salesloft/read_test.go
index 76ef1934..60aa544b 100644
--- a/providers/salesloft/read_test.go
+++ b/providers/salesloft/read_test.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/amp-labs/connectors/test/utils/mockutils"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockcond"
 	"github.com/amp-labs/connectors/test/utils/mockutils/mockserver"
diff --git a/providers/smartlead/read_test.go b/providers/smartlead/read_test.go
index b12460e2..8d0e6176 100644
--- a/providers/smartlead/read_test.go
+++ b/providers/smartlead/read_test.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/providers/zendesksupport/read_test.go b/providers/zendesksupport/read_test.go
index 75c67012..7bebf46c 100644
--- a/providers/zendesksupport/read_test.go
+++ b/providers/zendesksupport/read_test.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"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"
diff --git a/test/salesforce/event-flow/main.go b/test/salesforce/event-flow/main.go
index 76b02151..87633f97 100644
--- a/test/salesforce/event-flow/main.go
+++ b/test/salesforce/event-flow/main.go
@@ -3,13 +3,13 @@ package main
 import (
 	"context"
 	"fmt"
-	"log/slog"
 	"os/signal"
 	"strconv"
 	"strings"
 	"syscall"
 	"time"
 
+	"github.com/amp-labs/connectors/common/logging"
 	"github.com/amp-labs/connectors/providers/salesforce"
 	connTest "github.com/amp-labs/connectors/test/salesforce"
 	"github.com/amp-labs/connectors/test/utils"
@@ -50,26 +50,53 @@ func testCDC(conn *salesforce.Connector, ctx context.Context, creds salesforce.C
 
 	objectName := "Account"
 
-	_, err = testChangeDataCaptureChannelMembership(conn, ctx, cdcChannel.FullName, objectName)
+	membership, err := testChangeDataCaptureChannelMembership(conn, ctx, cdcChannel.FullName, objectName)
 	if err != nil {
 		return
 	}
 
 	if conn.RunEventRelay(ctx, evtCfg) != nil {
-		slog.Error("Error running event relay", "error", err)
+		logging.Logger(ctx).Error("Error running event relay", "error", err)
 		return
 	}
 
-	slog.Info("Event relay config updated", "state", "RUN")
+	logging.Logger(ctx).Info("Event relay config updated", "state", "RUN")
 
 	orgId, err := conn.GetOrganizationId(ctx)
 	if err != nil {
-		slog.Error("Failed to get orgId", "error", err)
+		logging.Logger(ctx).Error("Failed to get orgId", "error", err)
 		return
 	}
 
 	remoteResource := salesforce.GetRemoteResource(orgId, cdcChannel.FullName)
 	printWithField("RemoteResource", "resource", remoteResource)
+
+	resp, err := conn.DeleteEventChannelMember(ctx, membership.Id)
+	if err != nil {
+		logging.Logger(ctx).Error("Error deleting event channel member", "error", err)
+
+		return
+	}
+
+	printWithField("Event channel member deleted", "response", resp)
+
+	resp, err = conn.DeleteEventRelayConfig(ctx, evtCfg.Id)
+	if err != nil {
+		logging.Logger(ctx).Error("Error deleting event relay config", "error", err)
+
+		return
+	}
+
+	printWithField("Event relay config deleted", "response", resp)
+
+	resp, err = conn.DeleteEventChannel(ctx, cdcChannel.Id)
+	if err != nil {
+		logging.Logger(ctx).Error("Error deleting event channel", "error", err)
+
+		return
+	}
+
+	printWithField("Event channel deleted", "response", resp)
 }
 
 func testChangeDataCaptureChannelMembership(conn *salesforce.Connector, ctx context.Context, channelName string, objecName string) (*salesforce.EventChannelMember, error) {
@@ -86,7 +113,7 @@ func testChangeDataCaptureChannelMembership(conn *salesforce.Connector, ctx cont
 
 	newChannelMember, err := conn.CreateEventChannelMember(ctx, member)
 	if err != nil {
-		slog.Error("Error event channel member", "error", err)
+		logging.Logger(ctx).Error("Error event channel member", "error", err)
 
 		return nil, err
 	}
@@ -107,7 +134,7 @@ func TestCDCChannel(conn *salesforce.Connector, ctx context.Context, channelName
 
 	newChannel, err := conn.CreateEventChannel(ctx, channel)
 	if err != nil {
-		slog.Error("Error creating data channel", "error", err)
+		logging.Logger(ctx).Error("Error creating data channel", "error", err)
 		return nil, err
 	}
 
@@ -116,27 +143,6 @@ func TestCDCChannel(conn *salesforce.Connector, ctx context.Context, channelName
 	return newChannel, nil
 }
 
-func TestPlatformEventChannel(conn *salesforce.Connector, ctx context.Context, channelName string) (*salesforce.EventChannel, error) {
-	channel := &salesforce.EventChannel{
-		FullName: getChannelName(channelName),
-		Metadata: &salesforce.EventChannelMetadata{
-			ChannelType: "event",
-			Label:       "Test Event Channel",
-		},
-	}
-
-	newChannel, err := conn.CreateEventChannel(ctx, channel)
-	if err != nil {
-		slog.Error("Error creating event channel", "error", err)
-
-		return nil, err
-	}
-
-	printWithField("Platform Event channel created", "body", newChannel)
-
-	return newChannel, nil
-}
-
 func TestEventRelayConfig(conn *salesforce.Connector, ctx context.Context, cred salesforce.Credential, channel *salesforce.EventChannel, uniqueString string) (*salesforce.EventRelayConfig, error) {
 	evtCfg := &salesforce.EventRelayConfig{
 		FullName: "TestEventRelayConfig" + uniqueString,
@@ -148,7 +154,7 @@ func TestEventRelayConfig(conn *salesforce.Connector, ctx context.Context, cred
 
 	newEvtCfg, err := conn.CreateEventRelayConfig(ctx, evtCfg)
 	if err != nil {
-		slog.Error("Error event relay config", "error", err)
+		logging.Logger(ctx).Error("Error event relay config", "error", err)
 
 		return nil, err
 	}
@@ -159,20 +165,6 @@ func TestEventRelayConfig(conn *salesforce.Connector, ctx context.Context, cred
 	return newEvtCfg, nil
 }
 
-func testOrganizationId(conn *salesforce.Connector, ctx context.Context) (string, error) {
-	fmt.Println("-----------------Testing Organization Id-----------------")
-
-	orgId, err := conn.GetOrganizationId(ctx)
-	if err != nil {
-		slog.Error("Error querying org", "error", err)
-		return "", err
-	}
-
-	printWithField("Org Id created", "id", orgId)
-
-	return orgId, nil
-}
-
 func TestCredentialsLegacy(conn *salesforce.Connector, ctx context.Context, uniqueString string) (*salesforce.NamedCredential, error) {
 	fmt.Println("-----------------Testing Named Credential Legacy-----------------")
 
@@ -191,7 +183,7 @@ func TestCredentialsLegacy(conn *salesforce.Connector, ctx context.Context, uniq
 
 	newNamedCred, err := conn.CreateNamedCredential(ctx, namedCred)
 	if err != nil {
-		slog.Error("Error named cred", "error", err)
+		logging.Logger(ctx).Error("Error named cred", "error", err)
 		return nil, err
 	}
 
@@ -201,7 +193,8 @@ func TestCredentialsLegacy(conn *salesforce.Connector, ctx context.Context, uniq
 }
 
 func printWithField(header string, key string, v interface{}) {
-	slog.Info(header, key, fmt.Sprintf("%+v", v))
+	ctx := context.Background()
+	logging.Logger(ctx).Info(header, key, fmt.Sprintf("%+v", v))
 }
 
 func isCustomObject(objName string) bool {
@@ -236,22 +229,6 @@ func getRawChannelNameFromChannel(channelName string) string {
 	return channelName
 }
 
-func getRawChannelNameFromObject(objectName string) string {
-	if strings.HasSuffix(objectName, "__e") {
-		return removeSuffix(objectName, 3)
-	}
-
-	return objectName
-}
-
-func getPEChannelMembershipName(channelName, eventName string) string {
-	return fmt.Sprintf("%s%sChannelEvent_e", channelName, eventName)
-}
-
 func getCDCChannelMembershipName(rawChannelName string, eventName string) string {
 	return fmt.Sprintf("%s_chn_%s", rawChannelName, eventName)
 }
-
-func getRawPEName(peName string) string {
-	return removeSuffix(peName, 3)
-}
diff --git a/test/salesforce/subscribe/register.go b/test/salesforce/subscribe/register.go
new file mode 100644
index 00000000..6cc3ab1f
--- /dev/null
+++ b/test/salesforce/subscribe/register.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"os"
+	"os/signal"
+	"strconv"
+	"syscall"
+	"time"
+
+	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/logging"
+	"github.com/amp-labs/connectors/providers/salesforce"
+	connTest "github.com/amp-labs/connectors/test/salesforce"
+	"github.com/amp-labs/connectors/test/utils"
+)
+
+func main() {
+	// Handle Ctrl-C gracefully.
+	ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
+	defer done()
+
+	// Set up slog logging.
+	utils.SetupLogging()
+
+	conn := connTest.GetSalesforceConnector(ctx)
+
+	uniqueString := strconv.Itoa(int(time.Now().UnixMilli()))
+
+	arn := os.Getenv("AWS_NAMED_CRED_ARN")
+
+	params := &common.SubscriptionRegistrationParams{
+		Request: &salesforce.RegistrationParams{
+			UniqueRef:             "Amp" + uniqueString,
+			Label:                 "Amp" + uniqueString,
+			AwsNamedCredentialArn: arn,
+		},
+	}
+
+	result, err := conn.Register(ctx, params)
+	if err != nil {
+		logging.Logger(ctx).Error("Error registering", "error", err)
+		return
+	}
+
+	fmt.Println("Registration result:", prettyPrint(result))
+
+	if err := conn.DeleteRegistration(ctx, result); err != nil {
+		logging.Logger(ctx).Error("Error rolling back registration", "error", err)
+
+		return
+	}
+
+	fmt.Println("Delete successful")
+}
+
+func prettyPrint(v any) string {
+	jsonBytes, err := json.MarshalIndent(v, "", "  ")
+	if err != nil {
+		panic(err)
+	}
+
+	return string(jsonBytes)
+}
diff --git a/test/utils/searcher/search.go b/test/utils/searcher/search.go
index 68a433b7..475004a6 100644
--- a/test/utils/searcher/search.go
+++ b/test/utils/searcher/search.go
@@ -6,7 +6,7 @@ import (
 	"log/slog"
 
 	"github.com/amp-labs/connectors/common"
-	"github.com/amp-labs/connectors/common/jsonquery"
+	"github.com/amp-labs/connectors/internal/jsonquery"
 	"github.com/amp-labs/connectors/test/utils"
 	"github.com/spyzhov/ajson"
 )
@@ -47,31 +47,31 @@ func Find[T any](res *common.ReadResult, keys []Key, value T) map[string]any {
 		for _, key := range keys {
 			switch key.Type {
 			case String:
-				actual, err := jsonquery.New(node).Str(key.At, false)
+				actual, err := jsonquery.New(node).StringRequired(key.At)
 				if err != nil {
 					slog.Warn("string", "error", err)
 
 					continue
 				}
 
-				if fmt.Sprintf("%v", *actual) == fmt.Sprintf("%v", value) {
+				if fmt.Sprintf("%v", actual) == fmt.Sprintf("%v", value) {
 					return data.Fields
 				}
 			case Integer:
-				actual, err := jsonquery.New(node).Integer(key.At, false)
+				actual, err := jsonquery.New(node).IntegerRequired(key.At)
 				if err != nil {
 					slog.Warn("integer", "error", err)
 
 					continue
 				}
 
-				if fmt.Sprintf("%v", *actual) == fmt.Sprintf("%v", value) {
+				if fmt.Sprintf("%v", actual) == fmt.Sprintf("%v", value) {
 					return data.Fields
 				}
 			case Array:
 				var nodes []*ajson.Node
 
-				nodes, err = jsonquery.New(node).Array(key.At, false)
+				nodes, err = jsonquery.New(node).ArrayRequired(key.At)
 				if err != nil {
 					slog.Warn("array", "error", err)
 
@@ -82,7 +82,7 @@ func Find[T any](res *common.ReadResult, keys []Key, value T) map[string]any {
 			case Object:
 				fallthrough
 			default:
-				node, err = jsonquery.New(node).Object(key.At, false)
+				node, err = jsonquery.New(node).ObjectRequired(key.At)
 				if err != nil {
 					slog.Warn("object", "error", err)
 
diff --git a/test/zoom/read/main.go b/test/zoom/read/main.go
index 195c76e2..4f9120b6 100644
--- a/test/zoom/read/main.go
+++ b/test/zoom/read/main.go
@@ -17,7 +17,6 @@ import (
 var objectName = "users" // nolint: gochecknoglobals
 
 func main() {
-
 	ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
 	defer done()
 	// Set up slog logging.
@@ -36,5 +35,4 @@ func main() {
 
 	fmt.Println("Reading users..")
 	utils.DumpJSON(res, os.Stdout)
-
 }
diff --git a/test/zoom/write/main.go b/test/zoom/write/main.go
index 2418ac04..64823546 100644
--- a/test/zoom/write/main.go
+++ b/test/zoom/write/main.go
@@ -72,5 +72,4 @@ func createUser(ctx context.Context, conn *zoom.Connector, payload *CreateUserPa
 	}
 
 	utils.DumpJSON(res, os.Stdout)
-
 }