Repository: amp-labs/connectors
Pull request #479 (2024-06-05 12:38:14)

fix: pass API version Header for all Intercom methods
As I recall from the documentation their server defaults to some API version. Since we are passing it to one method all other methods should be consistent.

Linter: https://github.com/amp-labs/connectors/pull/480


Patch:
diff --git a/README.md b/README.md
index 9017d355..fdec9bde 100644
--- a/README.md
+++ b/README.md
@@ -122,13 +122,13 @@ If you don't, the provider info will be incomplete and the connector will not wo
 For example, a provider may use `{{workspace}}` in an option (maybe in the base URL) which needs to be replaced with an actual customer instance name. In that case, you would initialize the connector like this:
 
 ```go
-conn, err := basic.NewConnector(
+conn, err := connector.NewConnector(
     providers.SomeProvider,
-    basic.WithClient(context.Background(), http.DefaultClient, cfg, tok),
+    connector.WithClient(context.Background(), http.DefaultClient, cfg, tok),
     
     // WithCatalogSubstitutions allows you to replace placeholders in the catalog (providers.yaml) with actual values.
-    basic.WithCatalogSubstitutions(map[string]string{
-		"workspace": "customer-instance-ref"
+    connector.WithCatalogSubstitutions(map[string]string{
+		"workspace": "customer-instance-ref",
 	}),
 )
 ```
diff --git a/common/types.go b/common/types.go
index d955b45d..db632e52 100644
--- a/common/types.go
+++ b/common/types.go
@@ -83,7 +83,7 @@ var (
 	// ErrMetadataLoadFailure is returned when files that contain metadata for a connector cannot be loaded.
 	ErrMetadataLoadFailure = errors.New("cannot load metadata")
 
-	// ErrEmptyResponse is returned when the jsonResponse is nil.
+	// ErrEmptyJSONHTTPResponse is returned when the JSONHTTPResponse is nil.
 	ErrEmptyJSONHTTPResponse = errors.New("empty json http response")
 )
 
diff --git a/go.mod b/go.mod
index 2032b241..52c1faa5 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/iancoleman/strcase v0.3.0
 	github.com/joho/godotenv v1.5.1
 	github.com/spf13/cobra v1.8.0
-	github.com/spf13/viper v1.18.2
+	github.com/spf13/viper v1.19.0
 	github.com/spyzhov/ajson v0.9.1
 	github.com/stretchr/testify v1.9.0
 	github.com/subchen/go-xmldom v1.1.2
@@ -30,7 +30,7 @@ require (
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
-	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 	github.com/sagikazarmark/locafero v0.4.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
diff --git a/go.sum b/go.sum
index 54d1883f..58361400 100644
--- a/go.sum
+++ b/go.sum
@@ -45,8 +45,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
-github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -67,13 +67,14 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
 github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
-github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
 github.com/spyzhov/ajson v0.9.1 h1:izsDkOC9yznlpA2BxJcrkr8Q5Gyjf43yeSc2Wz0g/aU=
 github.com/spyzhov/ajson v0.9.1/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
diff --git a/gong/parse.go b/gong/parse.go
index c35fa20f..49635348 100644
--- a/gong/parse.go
+++ b/gong/parse.go
@@ -28,9 +28,7 @@ func getNextRecordsURL(node *ajson.Node) (string, error) {
 
 // getRecords returns the records from the response.
 func getRecords(node *ajson.Node, objectName string) ([]map[string]interface{}, error) {
-
 	records, err := node.GetKey(objectName)
-
 	if err != nil {
 		return nil, ErrNotArray
 	}
diff --git a/gong/read.go b/gong/read.go
index 647b6391..d4e319f0 100644
--- a/gong/read.go
+++ b/gong/read.go
@@ -20,7 +20,7 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		return nil, err
 	}
 
-	if len(config.NextPage) != 0 { //not the first page, add a cursor
+	if len(config.NextPage) != 0 { // not the first page, add a cursor
 		fullURL = fullURL + "?cursor=" + config.NextPage.String()
 	}
 
@@ -37,10 +37,7 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		func(node *ajson.Node) ([]map[string]interface{}, error) {
 			return getRecords(node, config.ObjectName)
 		},
-		func(node *ajson.Node) (string, error) {
-			return getNextRecordsURL(node)
-		},
-
+		getNextRecordsURL,
 		getMarshaledData,
 		fields,
 	)
diff --git a/intercom/connector.go b/intercom/connector.go
index 322a73d2..88fbeaf3 100644
--- a/intercom/connector.go
+++ b/intercom/connector.go
@@ -12,6 +12,11 @@ import (
 
 const apiVersion = "2.11"
 
+var apiVersionHeader = common.Header{ // nolint:gochecknoglobals
+	Key:   "Intercom-Version",
+	Value: apiVersion,
+}
+
 type Connector struct {
 	BaseURL string
 	Client  *common.JSONHTTPClient
diff --git a/intercom/delete.go b/intercom/delete.go
index 9c605224..a916b522 100644
--- a/intercom/delete.go
+++ b/intercom/delete.go
@@ -24,7 +24,7 @@ func (c *Connector) Delete(ctx context.Context, config common.DeleteParams) (*co
 
 	// Server usually responds with data indicating resource id that was just removed,
 	// or it returns the same payload as during Get/Post/Put requests
-	_, err = c.Client.Delete(ctx, url.String())
+	_, err = c.Client.Delete(ctx, url.String(), apiVersionHeader)
 	if err != nil {
 		return nil, err
 	}
diff --git a/intercom/read.go b/intercom/read.go
index bd012b4d..5a094f8a 100644
--- a/intercom/read.go
+++ b/intercom/read.go
@@ -14,10 +14,7 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		return nil, err
 	}
 
-	rsp, err := c.Client.Get(ctx, link.String(), common.Header{
-		Key:   "Intercom-Version",
-		Value: apiVersion,
-	})
+	rsp, err := c.Client.Get(ctx, link.String(), apiVersionHeader)
 	if err != nil {
 		return nil, err
 	}
diff --git a/intercom/write.go b/intercom/write.go
index c6a949ac..3b5ee9f5 100644
--- a/intercom/write.go
+++ b/intercom/write.go
@@ -30,7 +30,7 @@ func (c *Connector) Write(ctx context.Context, config common.WriteParams) (*comm
 		url.AddPath(config.RecordId)
 	}
 
-	res, err := write(ctx, url.String(), config.RecordData)
+	res, err := write(ctx, url.String(), config.RecordData, apiVersionHeader)
 	if err != nil {
 		return nil, err
 	}
diff --git a/providers/catalog.go b/providers/catalog.go
index 671c4391..a9e09b46 100644
--- a/providers/catalog.go
+++ b/providers/catalog.go
@@ -5,11 +5,15 @@ package providers
 // ================================================================================
 
 const (
+	AcuityScheduling        Provider = "acuityScheduling"
+	Aha                     Provider = "aha"
+	Aircall                 Provider = "aircall"
 	Airtable                Provider = "airtable"
-	AWeber                  Provider = "aWeber"
 	Asana                   Provider = "asana"
 	Atlassian               Provider = "atlassian"
 	Attio                   Provider = "attio"
+	AWeber                  Provider = "aWeber"
+	Basecamp                Provider = "basecamp"
 	Box                     Provider = "box"
 	Calendly                Provider = "calendly"
 	CampaignMonitor         Provider = "campaignMonitor"
@@ -21,27 +25,32 @@ const (
 	Discord                 Provider = "discord"
 	Docusign                Provider = "docusign"
 	DocusignDeveloper       Provider = "docusignDeveloper"
+	Drift                   Provider = "drift"
 	Dropbox                 Provider = "dropbox"
 	DropboxSign             Provider = "dropboxSign"
+	DynamicsBusinessCentral Provider = "dynamicsBusinessCentral"
+	DynamicsCRM             Provider = "dynamicsCRM"
 	Facebook                Provider = "facebook"
 	Figma                   Provider = "figma"
+	Formstack               Provider = "formstack"
 	Gainsight               Provider = "gainsight"
 	GetResponse             Provider = "getResponse"
+	Github                  Provider = "github"
 	Gmail                   Provider = "gmail"
 	Gong                    Provider = "gong"
-	IroncladDemo            Provider = "ironcladDemo"
-	IroncladEU              Provider = "ironcladEU"
-	Ironclad                Provider = "ironclad"
 	Google                  Provider = "google"
 	GoogleContacts          Provider = "googleContacts"
 	HelpScoutMailbox        Provider = "helpScoutMailbox"
 	Hubspot                 Provider = "hubspot"
+	Instagram               Provider = "instagram"
 	Intercom                Provider = "intercom"
+	Ironclad                Provider = "ironclad"
+	IroncladDemo            Provider = "ironcladDemo"
+	IroncladEU              Provider = "ironcladEU"
 	Keap                    Provider = "keap"
 	Klaviyo                 Provider = "klaviyo"
 	LinkedIn                Provider = "linkedIn"
-	DynamicsBusinessCentral Provider = "dynamicsBusinessCentral"
-	DynamicsCRM             Provider = "dynamicsCRM"
+	Microsoft               Provider = "microsoft"
 	Miro                    Provider = "miro"
 	Mock                    Provider = "mock"
 	Monday                  Provider = "monday"
@@ -53,10 +62,13 @@ const (
 	RingCentral             Provider = "ringCentral"
 	Salesforce              Provider = "salesforce"
 	Salesloft               Provider = "salesloft"
+	Seismic                 Provider = "seismic"
 	Sellsy                  Provider = "sellsy"
 	ServiceNow              Provider = "serviceNow"
+	SharePoint              Provider = "sharePoint"
 	Slack                   Provider = "slack"
 	Smartsheet              Provider = "smartsheet"
+	SnapchatAds             Provider = "snapchatAds"
 	StackExchange           Provider = "stackExchange"
 	TeamleaderCRM           Provider = "teamleaderCRM"
 	Timely                  Provider = "timely"
@@ -69,16 +81,6 @@ const (
 	ZohoCRM                 Provider = "zohoCRM"
 	Zoom                    Provider = "zoom"
 	Zuora                   Provider = "zuora"
-	Aircall                 Provider = "aircall"
-	Drift                   Provider = "drift"
-	Microsoft               Provider = "microsoft"
-	Formstack               Provider = "formstack"
-	Aha                     Provider = "aha"
-	SnapchatAds             Provider = "snapchatAds"
-	Instagram               Provider = "instagram"
-	Seismic                 Provider = "seismic"
-	Github                  Provider = "github"
-	Basecamp                Provider = "basecamp"
 )
 
 // ================================================================================
@@ -182,6 +184,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://accounts.salesloft.com/oauth/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -209,6 +212,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.outreach.io/oauth/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -560,6 +564,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://app.asana.com/-/oauth_token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ConsumerRefField: "data.id",
 			},
@@ -616,6 +621,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.notion.com/v1/oauth/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ConsumerRefField:  "owner.user.id",
 				WorkspaceRefField: "workspace_id",
@@ -644,6 +650,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://app.gong.io/oauth2/generate-customer-token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -671,6 +678,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://zoom.us/oauth/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -698,6 +706,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.intercom.io/auth/eagle/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -722,6 +731,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://account.docusign.com/oauth/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -800,6 +810,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.createsend.com/oauth/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -824,6 +835,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.getresponse.com/v3/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -848,6 +860,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://auth.aweber.com/oauth2/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -926,6 +939,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://login.microsoftonline.com/{{.workspace}}/oauth2/v2.0/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: true,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -953,6 +967,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://{{.workspace}}.gainsightcloud.com/v1/users/oauth/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: true,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -1053,6 +1068,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://public-api.wordpress.com/oauth2/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -1871,6 +1887,34 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 		},
 	},
 
+	SharePoint: {
+		AuthType: Oauth2,
+		BaseURL:  "https://{{.workspace}}.sharepoint.com/_api",
+		OauthOpts: &OauthOpts{
+			GrantType:              AuthorizationCode,
+			AuthURL:                "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
+			TokenURL:               "https://login.microsoftonline.com/common/oauth2/v2.0/token",
+			ExplicitScopesRequired: true,
+			// TODO: Switch to post-auth metadata collection
+			ExplicitWorkspaceRequired: true,
+			TokenMetadataFields: TokenMetadataFields{
+				ScopesField: "scope",
+			},
+		},
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     true,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+	},
+
 	// Instagram Configuration
 	// TODO: Supports only short-lived tokens
 	Instagram: {
@@ -2002,6 +2046,31 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 		},
 	},
 
+	// AcuityScheduling Configuration
+	AcuityScheduling: {
+		AuthType: Oauth2,
+		BaseURL:  "https://acuityscheduling.com",
+		OauthOpts: &OauthOpts{
+			GrantType:                 AuthorizationCode,
+			AuthURL:                   "https://acuityscheduling.com/oauth2/authorize",
+			TokenURL:                  "https://acuityscheduling.com/oauth2/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,
+		},
+	},
+
 	// Basecamp Configuration
 	// The wokspace in baseURL should be mapped to accounts.id
 	Basecamp: {
diff --git a/providers/types.gen.go b/providers/types.gen.go
index 61303a72..3306c205 100644
--- a/providers/types.gen.go
+++ b/providers/types.gen.go
@@ -43,11 +43,11 @@ type CatalogType map[string]ProviderInfo
 // OauthOpts defines model for OauthOpts.
 type OauthOpts struct {
 	AuthURL                   string              `json:"authURL" validate:"required"`
+	TokenURL                  string              `json:"tokenURL" validate:"required"`
 	ExplicitScopesRequired    bool                `json:"explicitScopesRequired"`
 	ExplicitWorkspaceRequired bool                `json:"explicitWorkspaceRequired"`
 	GrantType                 OauthOptsGrantType  `json:"grantType"`
 	TokenMetadataFields       TokenMetadataFields `json:"tokenMetadataFields"`
-	TokenURL                  string              `json:"tokenURL" validate:"required"`
 }
 
 // OauthOptsGrantType defines model for OauthOpts.GrantType.
diff --git a/providers/utils.go b/providers/utils.go
index 97cda7dc..39519160 100644
--- a/providers/utils.go
+++ b/providers/utils.go
@@ -1,3 +1,4 @@
+// nolint:ireturn
 package providers
 
 import (


Test patch:
diff --git a/gong/read_test.go b/gong/read_test.go
index 0c464f41..e27fad94 100644
--- a/gong/read_test.go
+++ b/gong/read_test.go
@@ -117,7 +117,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop
 		},
 
 		{
-			name:  "Succesful read with 2 entries and cursor for next page",
+			name:  "Successful read with 2 entries and cursor for next page",
 			input: common.ReadParams{ObjectName: "calls"},
 			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
diff --git a/intercom/delete_test.go b/intercom/delete_test.go
index 8135c7df..8542f8d8 100644
--- a/intercom/delete_test.go
+++ b/intercom/delete_test.go
@@ -69,7 +69,9 @@ func TestDelete(t *testing.T) { // nolint:funlen,cyclop
 			input: common.DeleteParams{ObjectName: "articles", RecordId: "9333415"},
 			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.Header().Set("Content-Type", "application/json")
-				mockutils.RespondNoContentForMethod(w, r, "DELETE")
+				mockutils.RespondToHeader(w, r, testApiVersionHeader, func() {
+					mockutils.RespondNoContentForMethod(w, r, "DELETE")
+				})
 			})),
 			expected:     &common.DeleteResult{Success: true},
 			expectedErrs: nil,
diff --git a/intercom/read_test.go b/intercom/read_test.go
index 88c37905..bd74e486 100644
--- a/intercom/read_test.go
+++ b/intercom/read_test.go
@@ -16,6 +16,12 @@ import (
 	"github.com/go-test/deep"
 )
 
+var testApiVersionHeader = http.Header{} // nolint:gochecknoglobals
+
+func init() {
+	testApiVersionHeader.Add("Intercom-Version", "2.11")
+}
+
 func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 	t.Parallel()
 
@@ -93,6 +99,22 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			},
 			expectedErrs: nil,
 		},
+		{
+			name: "API version header is passed as server request",
+			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.Header().Set("Content-Type", "application/json")
+				mockutils.RespondToHeader(w, r, testApiVersionHeader, func() {
+					w.WriteHeader(http.StatusOK)
+					_, _ = w.Write(responseNotesSecondPage)
+				})
+			})),
+			comparator: func(baseURL string, actual, expected *common.ReadResult) bool {
+				// response doesn't matter much, as soon as we don't have errors we are good
+				return actual.Done == expected.Done
+			},
+			expected:     &common.ReadResult{Done: true},
+			expectedErrs: nil,
+		},
 		{
 			name: "Next page URL is resolved, when provided with a string",
 			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/intercom/write_test.go b/intercom/write_test.go
index 2010abf9..5be321e8 100644
--- a/intercom/write_test.go
+++ b/intercom/write_test.go
@@ -84,6 +84,22 @@ func TestWrite(t *testing.T) { // nolint:funlen,cyclop
 			expected:     &common.WriteResult{Success: true},
 			expectedErrs: nil,
 		},
+		{
+			name:  "API version header is passed as server request on POST",
+			input: common.WriteParams{ObjectName: "articles"},
+			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.Header().Set("Content-Type", "application/json")
+				mockutils.RespondToHeader(w, r, testApiVersionHeader, func() {
+					w.WriteHeader(http.StatusOK)
+					_, _ = w.Write(createArticle)
+				})
+			})),
+			comparator: func(actual, expected *common.WriteResult) bool {
+				return actual.Success == expected.Success
+			},
+			expected:     &common.WriteResult{Success: true},
+			expectedErrs: nil,
+		},
 		{
 			name:  "Valid creation of an article",
 			input: common.WriteParams{ObjectName: "articles"},
diff --git a/test/gong/read/read.go b/test/gong/read/read.go
index 24f3cd7c..868d27a9 100644
--- a/test/gong/read/read.go
+++ b/test/gong/read/read.go
@@ -7,9 +7,8 @@ import (
 	"net/http"
 	"os"
 
-	"github.com/amp-labs/connectors/gong"
-
 	"github.com/amp-labs/connectors"
+	"github.com/amp-labs/connectors/gong"
 	"github.com/amp-labs/connectors/utils"
 	"github.com/joho/godotenv"
 )
@@ -19,7 +18,6 @@ const (
 )
 
 func GetGongConnector(ctx context.Context, filePath string) *gong.Connector {
-
 	registry := utils.NewCredentialsRegistry()
 
 	readers := []utils.Reader{
@@ -73,7 +71,6 @@ func main() {
 }
 
 func mainFn() int {
-
 	handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
 		Level: slog.LevelDebug,
 	})
diff --git a/test/utils/mockutils/server.go b/test/utils/mockutils/server.go
index 8b45a00b..66124a3c 100644
--- a/test/utils/mockutils/server.go
+++ b/test/utils/mockutils/server.go
@@ -22,11 +22,42 @@ func RespondToMethod(w http.ResponseWriter, r *http.Request, methodName string,
 		WriteBody(w, fmt.Sprintf(`{
 			"error": {
 				"code": "from test",
-				"message":"test server expected %v request"
+				"message": "test server expected %v request"
 			}}`, methodName))
 	}
 }
 
+func RespondToHeader(w http.ResponseWriter, r *http.Request, header http.Header, onSuccess func()) {
+	// if some headers are missing we return error code so the test will fail
+	if missingHeader, ok := headerIsSubset(r.Header, header); ok {
+		// if method is matching headers
+		onSuccess()
+	} else {
+		w.WriteHeader(http.StatusBadRequest)
+		WriteBody(w, fmt.Sprintf(`{
+			"error": {
+				"code": "from test",
+				"message": "test server mismatching [%v] header"
+			}}`, missingHeader))
+	}
+}
+
 func WriteBody(w http.ResponseWriter, body string) {
 	_, _ = w.Write([]byte(body))
 }
+
+func headerIsSubset(superset, subset http.Header) (string, bool) {
+	for name, values := range subset {
+		superValues := make(map[string]bool)
+		for _, v := range superset.Values(name) {
+			superValues[v] = true
+		}
+		// every value of this header must be part of superset
+		for _, value := range values {
+			if _, found := superValues[value]; !found {
+				return name, false
+			}
+		}
+	}
+	return "", true
+}