Repository: amp-labs/connectors
Pull request #475 (2024-06-04 23:56:30)

build: re-enable linter in Github actions
The linter was somehow set to `continue-on-error: true` before, so the CI passes even when the linter fails. This PR re-enables the linter and also fixes existing lint issues. 

I also pinned golangci to v1.54 (same version we use in the server), not latest. 


Patch:
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 16113037..beb00199 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -12,6 +12,6 @@ jobs:
           cache: false
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
-        continue-on-error: true
+        continue-on-error: false
         with:
-          version: latest
+          version: v1.54
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/dynamicscrm/connector.go b/dynamicscrm/connector.go
index c770882d..26d0aa77 100644
--- a/dynamicscrm/connector.go
+++ b/dynamicscrm/connector.go
@@ -6,19 +6,14 @@ import (
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/interpreter"
-	"github.com/amp-labs/connectors/common/paramsbuilder"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/providers"
 )
 
-var DataModule = paramsbuilder.APIModule{ // nolint: gochecknoglobals
-	Label:   "api/data",
-	Version: "v9.2",
-}
+const apiVersion = "v9.2"
 
 type Connector struct {
 	BaseURL   string
-	Module    string
 	Client    *common.JSONHTTPClient
 	XMLClient *common.XMLHTTPClient
 }
@@ -36,7 +31,6 @@ func NewConnector(opts ...Option) (conn *Connector, outErr error) {
 
 	httpClient := params.Client.Caller
 	conn = &Connector{
-		Module: params.Module.Suffix,
 		Client: &common.JSONHTTPClient{
 			HTTPClient: httpClient,
 		},
@@ -66,11 +60,11 @@ func (c *Connector) Provider() providers.Provider {
 }
 
 func (c *Connector) String() string {
-	return fmt.Sprintf("%s.Connector[%s]", c.Provider(), c.Module)
+	return fmt.Sprintf("%s.Connector", c.Provider())
 }
 
 func (c *Connector) getURL(arg string) (*urlbuilder.URL, error) {
-	parts := []string{c.BaseURL, c.Module, arg}
+	parts := []string{c.BaseURL, apiVersion, arg}
 	filtered := make([]string, 0)
 
 	for _, part := range parts {
diff --git a/dynamicscrm/params.go b/dynamicscrm/params.go
index 4639f805..f03ebf73 100644
--- a/dynamicscrm/params.go
+++ b/dynamicscrm/params.go
@@ -22,7 +22,6 @@ type Option func(params *parameters)
 type parameters struct {
 	paramsbuilder.Client
 	paramsbuilder.Workspace
-	paramsbuilder.Module
 }
 
 func (p parameters) FromOptions(opts ...Option) (*parameters, error) {
@@ -38,7 +37,6 @@ func (p parameters) ValidateParams() error {
 	return errors.Join(
 		p.Client.ValidateParams(),
 		p.Workspace.ValidateParams(),
-		p.Module.ValidateParams(),
 	)
 }
 
@@ -61,9 +59,3 @@ func WithWorkspace(workspaceRef string) Option {
 		params.WithWorkspace(workspaceRef)
 	}
 }
-
-func WithModule(module paramsbuilder.APIModule) Option {
-	return func(params *parameters) {
-		params.WithModule(module)
-	}
-}
diff --git a/go.mod b/go.mod
index 2032b241..eda48727 100644
--- a/go.mod
+++ b/go.mod
@@ -11,11 +11,11 @@ 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
-	golang.org/x/oauth2 v0.20.0
+	golang.org/x/oauth2 v0.21.0
 )
 
 require (
@@ -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..a4988d12 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=
@@ -102,8 +103,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
 golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
-golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
-golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/metadata/index.json b/intercom/metadata/index.json
index fd2a2f3d..b0e758c6 100755
--- a/intercom/metadata/index.json
+++ b/intercom/metadata/index.json
@@ -1,140 +1,5 @@
 {
   "data": [
-    {
-      "name": "admin",
-      "displayName": "Admin",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Admins/admin"
-    },
-    {
-      "name": "article",
-      "displayName": "Article",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article"
-    },
-    {
-      "name": "article_list_item",
-      "displayName": "Articles",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article_list_item"
-    },
-    {
-      "name": "article_search_highlights",
-      "displayName": "Article Search Highlights",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article_search_highlights"
-    },
-    {
-      "name": "article_search_response",
-      "displayName": "Article Search Response",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article_search_response"
-    },
-    {
-      "name": "company",
-      "displayName": "Company",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Companies/company"
-    },
-    {
-      "name": "contact",
-      "displayName": "Contact",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Contacts/contact"
-    },
-    {
-      "name": "conversation",
-      "displayName": "Conversation",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Conversations/conversation"
-    },
-    {
-      "name": "data_attribute",
-      "displayName": "Data Attribute",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Data-Attributes/data_attribute"
-    },
-    {
-      "name": "data_event",
-      "displayName": "Data Event",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Data-Events/data_event"
-    },
-    {
-      "name": "data_export",
-      "displayName": "Data Export",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Data-Export/data_export"
-    },
-    {
-      "name": "collection",
-      "displayName": "Collection",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Help-Center/collection"
-    },
-    {
-      "name": "help_center",
-      "displayName": "Help Center",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Help-Center/help_center"
-    },
-    {
-      "name": "help_center_list",
-      "displayName": "Help Centers",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Help-Center/help_center_list"
-    },
-    {
-      "name": "message",
-      "displayName": "Message",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Messages/message"
-    },
-    {
-      "name": "news_item",
-      "displayName": "News Item",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/News/news_item"
-    },
-    {
-      "name": "newsfeed",
-      "displayName": "Newsfeed",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/News/newsfeed"
-    },
-    {
-      "name": "newsfeed_assignment",
-      "displayName": "Newsfeed Assignment",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/News/newsfeed_assignment"
-    },
-    {
-      "name": "note",
-      "displayName": "Note",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Notes/note"
-    },
-    {
-      "name": "segment",
-      "displayName": "Segment",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Segments/segment"
-    },
-    {
-      "name": "subscription_type",
-      "displayName": "Subscription Types",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Subscription-Types/subscription_type"
-    },
-    {
-      "name": "tag",
-      "displayName": "Tag",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tags/tag"
-    },
-    {
-      "name": "team",
-      "displayName": "Team",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Teams/team"
-    },
-    {
-      "name": "ticket",
-      "displayName": "Ticket",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket"
-    },
-    {
-      "name": "ticket_contacts",
-      "displayName": "Contacts",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket_contacts"
-    },
-    {
-      "name": "ticket_part",
-      "displayName": "Ticket Part",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket_part"
-    },
-    {
-      "name": "ticket_type",
-      "displayName": "Ticket Type",
-      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket_type"
-    },
     {
       "name": "activity_log",
       "displayName": "Activity Log",
@@ -155,6 +20,11 @@
       "displayName": "Addressable List",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/addressable_list"
     },
+    {
+      "name": "admin",
+      "displayName": "Admin",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Admins/admin"
+    },
     {
       "name": "admin_list",
       "displayName": "Admins",
@@ -185,6 +55,11 @@
       "displayName": "App",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/app"
     },
+    {
+      "name": "article",
+      "displayName": "Article",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article"
+    },
     {
       "name": "article_content",
       "displayName": "Article Content",
@@ -195,6 +70,21 @@
       "displayName": "Articles",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/article_list"
     },
+    {
+      "name": "article_list_item",
+      "displayName": "Articles",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article_list_item"
+    },
+    {
+      "name": "article_search_highlights",
+      "displayName": "Article Search Highlights",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article_search_highlights"
+    },
+    {
+      "name": "article_search_response",
+      "displayName": "Article Search Response",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Articles/article_search_response"
+    },
     {
       "name": "article_statistics",
       "displayName": "Article Statistics",
@@ -220,11 +110,21 @@
       "displayName": "Close Conversation Request",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/close_conversation_request"
     },
+    {
+      "name": "collection",
+      "displayName": "Collection",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Help-Center/collection"
+    },
     {
       "name": "collection_list",
       "displayName": "Collections",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/collection_list"
     },
+    {
+      "name": "company",
+      "displayName": "Company",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Companies/company"
+    },
     {
       "name": "company_attached_contacts",
       "displayName": "Company Attached Contacts",
@@ -245,6 +145,11 @@
       "displayName": "Company Scroll",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/company_scroll"
     },
+    {
+      "name": "contact",
+      "displayName": "Contact",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Contacts/contact"
+    },
     {
       "name": "contact_archived",
       "displayName": "Contact Archived",
@@ -360,6 +265,11 @@
       "displayName": "Content Source List",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/content_sources_list"
     },
+    {
+      "name": "conversation",
+      "displayName": "Conversation",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Conversations/conversation"
+    },
     {
       "name": "conversation_attachment_files",
       "displayName": "Conversation attachment files",
@@ -520,11 +430,21 @@
       "displayName": "customer_request",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/customer_request"
     },
+    {
+      "name": "data_attribute",
+      "displayName": "Data Attribute",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Data-Attributes/data_attribute"
+    },
     {
       "name": "data_attribute_list",
       "displayName": "Data Attribute List",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/data_attribute_list"
     },
+    {
+      "name": "data_event",
+      "displayName": "Data Event",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Data-Events/data_event"
+    },
     {
       "name": "data_event_list",
       "displayName": "Data Event List",
@@ -540,6 +460,11 @@
       "displayName": "Data Event Summary Item",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/data_event_summary_item"
     },
+    {
+      "name": "data_export",
+      "displayName": "Data Export",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Data-Export/data_export"
+    },
     {
       "name": "data_export_csv",
       "displayName": "Data Export CSV",
@@ -590,6 +515,16 @@
       "displayName": "Group Translated Content",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/group_translated_content"
     },
+    {
+      "name": "help_center",
+      "displayName": "Help Center",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Help-Center/help_center"
+    },
+    {
+      "name": "help_center_list",
+      "displayName": "Help Centers",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Help-Center/help_center_list"
+    },
     {
       "name": "intercom_version",
       "displayName": "intercom_version",
@@ -610,16 +545,41 @@
       "displayName": "Merge contact data",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/merge_contacts_request"
     },
+    {
+      "name": "message",
+      "displayName": "Message",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Messages/message"
+    },
     {
       "name": "multiple_filter_search_request",
       "displayName": "Multiple Filter Search Request",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/multiple_filter_search_request"
     },
+    {
+      "name": "news_item",
+      "displayName": "News Item",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/News/news_item"
+    },
     {
       "name": "news_item_request",
       "displayName": "Create News Item Request",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/news_item_request"
     },
+    {
+      "name": "newsfeed",
+      "displayName": "Newsfeed",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/News/newsfeed"
+    },
+    {
+      "name": "newsfeed_assignment",
+      "displayName": "Newsfeed Assignment",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/News/newsfeed_assignment"
+    },
+    {
+      "name": "note",
+      "displayName": "Note",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Notes/note"
+    },
     {
       "name": "note_list",
       "displayName": "Paginated Response",
@@ -670,6 +630,11 @@
       "displayName": "Search data",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/search_request"
     },
+    {
+      "name": "segment",
+      "displayName": "Segment",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Segments/segment"
+    },
     {
       "name": "segment_list",
       "displayName": "Segment List",
@@ -700,11 +665,21 @@
       "displayName": "Pagination: Starting After",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/starting_after_paging"
     },
+    {
+      "name": "subscription_type",
+      "displayName": "Subscription Types",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Subscription-Types/subscription_type"
+    },
     {
       "name": "subscription_type_list",
       "displayName": "Subscription Types",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/subscription_type_list"
     },
+    {
+      "name": "tag",
+      "displayName": "Tag",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tags/tag"
+    },
     {
       "name": "tag_company_request",
       "displayName": "Tag Company Request Payload",
@@ -725,6 +700,11 @@
       "displayName": "Tags",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/tags"
     },
+    {
+      "name": "team",
+      "displayName": "Team",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Teams/team"
+    },
     {
       "name": "team_list",
       "displayName": "Team List",
@@ -735,6 +715,16 @@
       "displayName": "Team Priority Level",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/team_priority_level"
     },
+    {
+      "name": "ticket",
+      "displayName": "Ticket",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket"
+    },
+    {
+      "name": "ticket_contacts",
+      "displayName": "Contacts",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket_contacts"
+    },
     {
       "name": "ticket_custom_attributes",
       "displayName": "Ticket Attributes",
@@ -745,6 +735,11 @@
       "displayName": "Ticket List",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/ticket_list"
     },
+    {
+      "name": "ticket_part",
+      "displayName": "Ticket Part",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket_part"
+    },
     {
       "name": "ticket_part_author",
       "displayName": "Ticket part author",
@@ -765,6 +760,11 @@
       "displayName": "Ticket Attributes",
       "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Models/ticket_request_custom_attributes"
     },
+    {
+      "name": "ticket_type",
+      "displayName": "Ticket Type",
+      "url": "https://developers.intercom.com/docs/references/rest-api/api.intercom.io/Tickets/ticket_type"
+    },
     {
       "name": "ticket_type_attribute",
       "displayName": "Ticket Type Attribute",
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 ecd3579c..c3da0afa 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,11 +62,15 @@ 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"
+	SurveyMonkey            Provider = "surveyMonkey"
 	TeamleaderCRM           Provider = "teamleaderCRM"
 	Timely                  Provider = "timely"
 	Typeform                Provider = "typeform"
@@ -69,15 +82,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"
 )
 
 // ================================================================================
@@ -181,6 +185,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://accounts.salesloft.com/oauth/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -208,6 +213,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.outreach.io/oauth/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -309,7 +315,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 		},
 	},
 
-	// Wrikle configuration
+	// Wrike configuration
 	Wrike: {
 		AuthType: Oauth2,
 		BaseURL:  "https://www.wrike.com/api",
@@ -559,6 +565,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://app.asana.com/-/oauth_token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ConsumerRefField: "data.id",
 			},
@@ -615,6 +622,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",
@@ -643,6 +651,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",
 			},
@@ -670,6 +679,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://zoom.us/oauth/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -697,6 +707,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.intercom.io/auth/eagle/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -721,6 +732,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://account.docusign.com/oauth/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -799,6 +811,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.createsend.com/oauth/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -823,6 +836,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://api.getresponse.com/v3/token",
 			ExplicitScopesRequired:    false,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -847,6 +861,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://auth.aweber.com/oauth2/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 		},
 		Support: Support{
 			BulkWrite: BulkWriteSupport{
@@ -865,7 +880,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 	DynamicsCRM: {
 		DisplayName: "Microsoft Dynamics CRM",
 		AuthType:    Oauth2,
-		BaseURL:     "https://{{.workspace}}.api.crm.dynamics.com",
+		BaseURL:     "https://{{.workspace}}.api.crm.dynamics.com/api/data",
 		OauthOpts: &OauthOpts{
 			GrantType:              AuthorizationCode,
 			AuthURL:                "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
@@ -925,6 +940,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",
 			},
@@ -952,6 +968,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{
@@ -1052,6 +1069,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			TokenURL:                  "https://public-api.wordpress.com/oauth2/token",
 			ExplicitScopesRequired:    true,
 			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
 			TokenMetadataFields: TokenMetadataFields{
 				ScopesField: "scope",
 			},
@@ -1786,7 +1804,7 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 				Upsert: false,
 				Delete: false,
 			},
-			Proxy:     false,
+			Proxy:     true,
 			Read:      false,
 			Subscribe: false,
 			Write:     false,
@@ -1870,6 +1888,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: {
@@ -2000,4 +2046,80 @@ var catalog = CatalogType{ // nolint:gochecknoglobals
 			Write:     false,
 		},
 	},
+
+	// 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: {
+		AuthType: Oauth2,
+		BaseURL:  "https://3.basecampapi.com/{{.workspace}}",
+		OauthOpts: &OauthOpts{
+			GrantType:                 AuthorizationCode,
+			AuthURL:                   "https://launchpad.37signals.com/authorization/new?type=web_server",
+			TokenURL:                  "https://launchpad.37signals.com/authorization/token?type=refresh",
+			ExplicitScopesRequired:    false,
+			ExplicitWorkspaceRequired: false,
+		},
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+	},
+
+	// SurveyMonkey configuration file
+	SurveyMonkey: {
+		AuthType: Oauth2,
+		BaseURL:  "https://api.surveymonkey.com",
+		OauthOpts: &OauthOpts{
+			AuthURL:                   "https://api.surveymonkey.com/oauth/authorize",
+			TokenURL:                  "https://api.surveymonkey.com/oauth/token",
+			ExplicitScopesRequired:    false,
+			ExplicitWorkspaceRequired: false,
+			GrantType:                 AuthorizationCode,
+		},
+		Support: Support{
+			BulkWrite: BulkWriteSupport{
+				Insert: false,
+				Update: false,
+				Upsert: false,
+				Delete: false,
+			},
+			Proxy:     false,
+			Read:      false,
+			Subscribe: false,
+			Write:     false,
+		},
+	},
 }
diff --git a/providers/types.gen.go b/providers/types.gen.go
index 61303a72..bb44e512 100644
--- a/providers/types.gen.go
+++ b/providers/types.gen.go
@@ -42,7 +42,8 @@ type CatalogType map[string]ProviderInfo
 
 // OauthOpts defines model for OauthOpts.
 type OauthOpts struct {
-	AuthURL                   string              `json:"authURL" validate:"required"`
+	Audience                  string              `json:"audience,omitempty"`
+	AuthURL                   string              `json:"authURL,omitempty"`
 	ExplicitScopesRequired    bool                `json:"explicitScopesRequired"`
 	ExplicitWorkspaceRequired bool                `json:"explicitWorkspaceRequired"`
 	GrantType                 OauthOptsGrantType  `json:"grantType"`
@@ -65,7 +66,7 @@ type ProviderInfo struct {
 	// DisplayName The display name of the provider, if omitted, defaults to provider name.
 	DisplayName string     `json:"displayName,omitempty"`
 	Name        string     `json:"name"`
-	OauthOpts   *OauthOpts `json:"oauthOpts,omitempty" validate:"required"`
+	OauthOpts   *OauthOpts `json:"oauthOpts,omitempty"`
 
 	// PostAuthInfoNeeded If true, we require additional information after auth to start making requests.
 	PostAuthInfoNeeded bool         `json:"postAuthInfoNeeded,omitempty"`
diff --git a/providers/types.yaml b/providers/types.yaml
index e386e3da..1fc9e8e1 100644
--- a/providers/types.yaml
+++ b/providers/types.yaml
@@ -77,11 +77,8 @@ components:
 
     OauthOpts:
       type: object
-      x-oapi-codegen-extra-tags:
-        validate: required
       required:
           - grantType
-          - authURL
           - tokenURL
           - explicitScopesRequired
           - explicitWorkspaceRequired
@@ -93,8 +90,7 @@ components:
         authURL:
           type: string
           example: https://login.salesforce.com/services/oauth2/authorize
-          x-oapi-codegen-extra-tags:
-            validate: required
+          x-go-type-skip-optional-pointer: true
         tokenURL:
           type: string
           example: https://login.salesforce.com/services/oauth2/token
@@ -106,6 +102,10 @@ components:
         explicitWorkspaceRequired:
           type: boolean
           example: true
+        audience:
+          type: string
+          example: https://api.mparticle.com
+          x-go-type-skip-optional-pointer: true
         tokenMetadataFields:
           $ref: '#/components/schemas/TokenMetadataFields'
 
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 (
diff --git a/salesloft/connector.go b/salesloft/connector.go
index d3fedc59..7f15ec38 100644
--- a/salesloft/connector.go
+++ b/salesloft/connector.go
@@ -6,18 +6,14 @@ import (
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/interpreter"
-	"github.com/amp-labs/connectors/common/paramsbuilder"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/providers"
 )
 
-var DefaultModuleCRM = paramsbuilder.APIModule{ // nolint: gochecknoglobals
-	Version: "v2",
-}
+const apiVersion = "v2"
 
 type Connector struct {
 	BaseURL string
-	Module  string
 	Client  *common.JSONHTTPClient
 }
 
@@ -39,7 +35,6 @@ func NewConnector(opts ...Option) (conn *Connector, outErr error) {
 
 	httpClient := params.Client.Caller
 	conn = &Connector{
-		Module: params.Module.Suffix,
 		Client: &common.JSONHTTPClient{
 			HTTPClient: httpClient,
 		},
@@ -58,11 +53,11 @@ func (c *Connector) Provider() providers.Provider {
 }
 
 func (c *Connector) String() string {
-	return fmt.Sprintf("%s.Connector[%s]", c.Provider(), c.Module)
+	return fmt.Sprintf("%s.Connector", c.Provider())
 }
 
 func (c *Connector) getURL(arg string) (*urlbuilder.URL, error) {
-	parts := []string{c.BaseURL, c.Module, arg}
+	parts := []string{c.BaseURL, apiVersion, arg}
 	filtered := make([]string, 0)
 
 	for _, part := range parts {
diff --git a/salesloft/metadata/index.json b/salesloft/metadata/index.json
index 6f40c265..d2cc133f 100755
--- a/salesloft/metadata/index.json
+++ b/salesloft/metadata/index.json
@@ -1,20 +1,5 @@
 {
   "data": [
-    {
-      "name": "me-index",
-      "displayName": "Fetch current user",
-      "url": "https://developers.salesloft.com/docs/api/me-index"
-    },
-    {
-      "name": "team-index",
-      "displayName": "Fetch current team",
-      "url": "https://developers.salesloft.com/docs/api/team-index"
-    },
-    {
-      "name": "data-control-account-redaction-create",
-      "displayName": "Execute an account redaction workflow",
-      "url": "https://developers.salesloft.com/docs/api/data-control-account-redaction-create"
-    },
     {
       "name": "account-stages-index",
       "displayName": "List account stages",
@@ -41,20 +26,20 @@
       "url": "https://developers.salesloft.com/docs/api/account-upserts-create"
     },
     {
-      "name": "data-control-account-and-people-redaction-create",
-      "displayName": "Submit an account and people redaction  request",
-      "url": "https://developers.salesloft.com/docs/api/data-control-account-and-people-redaction-create"
+      "name": "accounts-create",
+      "displayName": "Create an account",
+      "url": "https://developers.salesloft.com/docs/api/accounts-create"
+    },
+    {
+      "name": "accounts-destroy",
+      "displayName": "Delete an account",
+      "url": "https://developers.salesloft.com/docs/api/accounts-destroy"
     },
     {
       "name": "accounts-index",
       "displayName": "List accounts",
       "url": "https://developers.salesloft.com/docs/api/accounts-index"
     },
-    {
-      "name": "accounts-create",
-      "displayName": "Create an account",
-      "url": "https://developers.salesloft.com/docs/api/accounts-create"
-    },
     {
       "name": "accounts-show",
       "displayName": "Fetch an account",
@@ -66,9 +51,14 @@
       "url": "https://developers.salesloft.com/docs/api/accounts-update"
     },
     {
-      "name": "accounts-destroy",
-      "displayName": "Delete an account",
-      "url": "https://developers.salesloft.com/docs/api/accounts-destroy"
+      "name": "action-details-call-instructions-index",
+      "displayName": "List call instructions",
+      "url": "https://developers.salesloft.com/docs/api/action-details-call-instructions-index"
+    },
+    {
+      "name": "action-details-call-instructions-show",
+      "displayName": "Fetch a call instructions",
+      "url": "https://developers.salesloft.com/docs/api/action-details-call-instructions-show"
     },
     {
       "name": "actions-index",
@@ -80,25 +70,40 @@
       "displayName": "Fetch an action",
       "url": "https://developers.salesloft.com/docs/api/actions-show"
     },
+    {
+      "name": "activities-calls-create",
+      "displayName": "Create a call",
+      "url": "https://developers.salesloft.com/docs/api/activities-calls-create"
+    },
+    {
+      "name": "activities-calls-index",
+      "displayName": "List calls",
+      "url": "https://developers.salesloft.com/docs/api/activities-calls-index"
+    },
+    {
+      "name": "activities-calls-show",
+      "displayName": "Fetch a call",
+      "url": "https://developers.salesloft.com/docs/api/activities-calls-show"
+    },
     {
       "name": "activities-create",
       "displayName": "Create an activity",
       "url": "https://developers.salesloft.com/docs/api/activities-create"
     },
     {
-      "name": "activity-histories-index",
-      "displayName": "List Past Activities",
-      "url": "https://developers.salesloft.com/docs/api/activity-histories-index"
+      "name": "activities-emails-index",
+      "displayName": "List emails",
+      "url": "https://developers.salesloft.com/docs/api/activities-emails-index"
     },
     {
-      "name": "bulk-jobs-update",
-      "displayName": "Update a bulk job",
-      "url": "https://developers.salesloft.com/docs/api/bulk-jobs-update"
+      "name": "activities-emails-show",
+      "displayName": "Fetch an email",
+      "url": "https://developers.salesloft.com/docs/api/activities-emails-show"
     },
     {
-      "name": "bulk-jobs-show",
-      "displayName": "Fetch a bulk job",
-      "url": "https://developers.salesloft.com/docs/api/bulk-jobs-show"
+      "name": "activity-histories-index",
+      "displayName": "List Past Activities",
+      "url": "https://developers.salesloft.com/docs/api/activity-histories-index"
     },
     {
       "name": "bulk-jobs-create",
@@ -125,6 +130,16 @@
       "displayName": "List job data for a completed bulk job.",
       "url": "https://developers.salesloft.com/docs/api/bulk-jobs-results-index"
     },
+    {
+      "name": "bulk-jobs-show",
+      "displayName": "Fetch a bulk job",
+      "url": "https://developers.salesloft.com/docs/api/bulk-jobs-show"
+    },
+    {
+      "name": "bulk-jobs-update",
+      "displayName": "Update a bulk job",
+      "url": "https://developers.salesloft.com/docs/api/bulk-jobs-update"
+    },
     {
       "name": "bulk-reschedule-tasks-create",
       "displayName": "Reschedule many tasks at once",
@@ -140,26 +155,26 @@
       "displayName": "Import cadences from JSON",
       "url": "https://developers.salesloft.com/docs/api/cadence-imports-create"
     },
-    {
-      "name": "cadence-memberships-index",
-      "displayName": "List cadence memberships",
-      "url": "https://developers.salesloft.com/docs/api/cadence-memberships-index"
-    },
     {
       "name": "cadence-memberships-create",
       "displayName": "Create a cadence membership",
       "url": "https://developers.salesloft.com/docs/api/cadence-memberships-create"
     },
-    {
-      "name": "cadence-memberships-show",
-      "displayName": "Fetch a cadence membership",
-      "url": "https://developers.salesloft.com/docs/api/cadence-memberships-show"
-    },
     {
       "name": "cadence-memberships-destroy",
       "displayName": "Delete a cadence membership",
       "url": "https://developers.salesloft.com/docs/api/cadence-memberships-destroy"
     },
+    {
+      "name": "cadence-memberships-index",
+      "displayName": "List cadence memberships",
+      "url": "https://developers.salesloft.com/docs/api/cadence-memberships-index"
+    },
+    {
+      "name": "cadence-memberships-show",
+      "displayName": "Fetch a cadence membership",
+      "url": "https://developers.salesloft.com/docs/api/cadence-memberships-show"
+    },
     {
       "name": "cadences-index",
       "displayName": "List cadences",
@@ -170,16 +185,16 @@
       "displayName": "Fetch a cadence",
       "url": "https://developers.salesloft.com/docs/api/cadences-show"
     },
-    {
-      "name": "calendar-events-upsert-create",
-      "displayName": "Upsert a calendar event",
-      "url": "https://developers.salesloft.com/docs/api/calendar-events-upsert-create"
-    },
     {
       "name": "calendar-events-index",
       "displayName": "List calendar events",
       "url": "https://developers.salesloft.com/docs/api/calendar-events-index"
     },
+    {
+      "name": "calendar-events-upsert-create",
+      "displayName": "Upsert a calendar event",
+      "url": "https://developers.salesloft.com/docs/api/calendar-events-upsert-create"
+    },
     {
       "name": "call-data-records-index",
       "displayName": "List call data records",
@@ -195,51 +210,16 @@
       "displayName": "List call dispositions",
       "url": "https://developers.salesloft.com/docs/api/call-dispositions-index"
     },
-    {
-      "name": "action-details-call-instructions-index",
-      "displayName": "List call instructions",
-      "url": "https://developers.salesloft.com/docs/api/action-details-call-instructions-index"
-    },
-    {
-      "name": "action-details-call-instructions-show",
-      "displayName": "Fetch a call instructions",
-      "url": "https://developers.salesloft.com/docs/api/action-details-call-instructions-show"
-    },
     {
       "name": "call-sentiments-index",
       "displayName": "List call sentiments",
       "url": "https://developers.salesloft.com/docs/api/call-sentiments-index"
     },
-    {
-      "name": "phone-numbers-caller-ids-index",
-      "displayName": "List caller ids",
-      "url": "https://developers.salesloft.com/docs/api/phone-numbers-caller-ids-index"
-    },
-    {
-      "name": "activities-calls-index",
-      "displayName": "List calls",
-      "url": "https://developers.salesloft.com/docs/api/activities-calls-index"
-    },
-    {
-      "name": "activities-calls-create",
-      "displayName": "Create a call",
-      "url": "https://developers.salesloft.com/docs/api/activities-calls-create"
-    },
-    {
-      "name": "activities-calls-show",
-      "displayName": "Fetch a call",
-      "url": "https://developers.salesloft.com/docs/api/activities-calls-show"
-    },
     {
       "name": "create-conversations-call",
       "displayName": "Create Conversations Call",
       "url": "https://developers.salesloft.com/docs/api/create-conversations-call"
     },
-    {
-      "name": "tasks-counts-index",
-      "displayName": "Fetch task counts",
-      "url": "https://developers.salesloft.com/docs/api/tasks-counts-index"
-    },
     {
       "name": "crm-activities-index",
       "displayName": "List crm activities",
@@ -260,26 +240,26 @@
       "displayName": "List crm users",
       "url": "https://developers.salesloft.com/docs/api/crm-users-index"
     },
-    {
-      "name": "custom-fields-index",
-      "displayName": "List custom fields",
-      "url": "https://developers.salesloft.com/docs/api/custom-fields-index"
-    },
     {
       "name": "custom-fields-create",
       "displayName": "Create a custom field",
       "url": "https://developers.salesloft.com/docs/api/custom-fields-create"
     },
-    {
-      "name": "custom-fields-show",
-      "displayName": "Fetch a custom field",
-      "url": "https://developers.salesloft.com/docs/api/custom-fields-show"
-    },
     {
       "name": "custom-fields-destroy",
       "displayName": "Delete a custom field",
       "url": "https://developers.salesloft.com/docs/api/custom-fields-destroy"
     },
+    {
+      "name": "custom-fields-index",
+      "displayName": "List custom fields",
+      "url": "https://developers.salesloft.com/docs/api/custom-fields-index"
+    },
+    {
+      "name": "custom-fields-show",
+      "displayName": "Fetch a custom field",
+      "url": "https://developers.salesloft.com/docs/api/custom-fields-show"
+    },
     {
       "name": "custom-roles-index",
       "displayName": "List custom roles",
@@ -290,6 +270,36 @@
       "displayName": "Fetch a custom role",
       "url": "https://developers.salesloft.com/docs/api/custom-roles-show"
     },
+    {
+      "name": "data-control-account-and-people-redaction-create",
+      "displayName": "Submit an account and people redaction  request",
+      "url": "https://developers.salesloft.com/docs/api/data-control-account-and-people-redaction-create"
+    },
+    {
+      "name": "data-control-account-redaction-create",
+      "displayName": "Execute an account redaction workflow",
+      "url": "https://developers.salesloft.com/docs/api/data-control-account-redaction-create"
+    },
+    {
+      "name": "data-control-people-soft-deletion-create",
+      "displayName": "Submit a people soft deletion request",
+      "url": "https://developers.salesloft.com/docs/api/data-control-people-soft-deletion-create"
+    },
+    {
+      "name": "data-control-requests-index",
+      "displayName": "Retrieve a list of Requests",
+      "url": "https://developers.salesloft.com/docs/api/data-control-requests-index"
+    },
+    {
+      "name": "data-control-requests-show",
+      "displayName": "Fetch a Request record",
+      "url": "https://developers.salesloft.com/docs/api/data-control-requests-show"
+    },
+    {
+      "name": "data-control-right-to-be-forgotten-create",
+      "displayName": "Submit a right to be forgotten request",
+      "url": "https://developers.salesloft.com/docs/api/data-control-right-to-be-forgotten-create"
+    },
     {
       "name": "email-missing-tags-create",
       "displayName": "Requests a check of the template for missed tags",
@@ -310,16 +320,6 @@
       "displayName": "Fetch an email template",
       "url": "https://developers.salesloft.com/docs/api/email-templates-show"
     },
-    {
-      "name": "activities-emails-index",
-      "displayName": "List emails",
-      "url": "https://developers.salesloft.com/docs/api/activities-emails-index"
-    },
-    {
-      "name": "activities-emails-show",
-      "displayName": "Fetch an email",
-      "url": "https://developers.salesloft.com/docs/api/activities-emails-show"
-    },
     {
       "name": "external-emails-create",
       "displayName": "Create an External Email",
@@ -330,6 +330,11 @@
       "displayName": "Create a group",
       "url": "https://developers.salesloft.com/docs/api/groups-create"
     },
+    {
+      "name": "groups-destroy",
+      "displayName": "Deletes a Group by ID",
+      "url": "https://developers.salesloft.com/docs/api/groups-destroy"
+    },
     {
       "name": "groups-index",
       "displayName": "List groups",
@@ -346,20 +351,20 @@
       "url": "https://developers.salesloft.com/docs/api/groups-update"
     },
     {
-      "name": "groups-destroy",
-      "displayName": "Deletes a Group by ID",
-      "url": "https://developers.salesloft.com/docs/api/groups-destroy"
+      "name": "imports-create",
+      "displayName": "Create an import",
+      "url": "https://developers.salesloft.com/docs/api/imports-create"
+    },
+    {
+      "name": "imports-destroy",
+      "displayName": "Delete an import",
+      "url": "https://developers.salesloft.com/docs/api/imports-destroy"
     },
     {
       "name": "imports-index",
       "displayName": "List imports",
       "url": "https://developers.salesloft.com/docs/api/imports-index"
     },
-    {
-      "name": "imports-create",
-      "displayName": "Create an import",
-      "url": "https://developers.salesloft.com/docs/api/imports-create"
-    },
     {
       "name": "imports-show",
       "displayName": "Fetch an import",
@@ -371,25 +376,85 @@
       "url": "https://developers.salesloft.com/docs/api/imports-update"
     },
     {
-      "name": "imports-destroy",
-      "displayName": "Delete an import",
-      "url": "https://developers.salesloft.com/docs/api/imports-destroy"
+      "name": "integrations-signals-registrations-create",
+      "displayName": "Create a Signal Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-create"
     },
     {
-      "name": "third-party-live-feed-items-create",
-      "displayName": "Create a live feed item",
-      "url": "https://developers.salesloft.com/docs/api/third-party-live-feed-items-create"
+      "name": "integrations-signals-registrations-destroy",
+      "displayName": "Deletes a Signal Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-destroy"
+    },
+    {
+      "name": "integrations-signals-registrations-index",
+      "displayName": "List Signal Registrations",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-index"
+    },
+    {
+      "name": "integrations-signals-registrations-plays-create",
+      "displayName": "Create a Play Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-create"
+    },
+    {
+      "name": "integrations-signals-registrations-plays-destroy",
+      "displayName": "Delete a Play Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-destroy"
+    },
+    {
+      "name": "integrations-signals-registrations-plays-index",
+      "displayName": "List Play Registrations",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-index"
+    },
+    {
+      "name": "integrations-signals-registrations-plays-show",
+      "displayName": "Show a Play Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-show"
+    },
+    {
+      "name": "integrations-signals-registrations-plays-update",
+      "displayName": "Update a Play Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-update"
+    },
+    {
+      "name": "integrations-signals-registrations-show",
+      "displayName": "Fetch a Signal Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-show"
+    },
+    {
+      "name": "integrations-signals-registrations-update",
+      "displayName": "Update a Signal Registration",
+      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-update"
     },
     {
       "name": "live-website-tracking-parameters-create",
       "displayName": "Create an Live Website Tracking Parameter",
       "url": "https://developers.salesloft.com/docs/api/live-website-tracking-parameters-create"
     },
+    {
+      "name": "me-index",
+      "displayName": "Fetch current user",
+      "url": "https://developers.salesloft.com/docs/api/me-index"
+    },
     {
       "name": "meetings-index",
       "displayName": "List meetings",
       "url": "https://developers.salesloft.com/docs/api/meetings-index"
     },
+    {
+      "name": "meetings-reschedule-links-show",
+      "displayName": "Fetch a reschedule meeting link",
+      "url": "https://developers.salesloft.com/docs/api/meetings-reschedule-links-show"
+    },
+    {
+      "name": "meetings-settings-searches-create",
+      "displayName": "List meeting settings",
+      "url": "https://developers.salesloft.com/docs/api/meetings-settings-searches-create"
+    },
+    {
+      "name": "meetings-settings-update",
+      "displayName": "Update a meeting setting",
+      "url": "https://developers.salesloft.com/docs/api/meetings-settings-update"
+    },
     {
       "name": "meetings-update",
       "displayName": "Update a meeting",
@@ -400,16 +465,21 @@
       "displayName": "Fetch the MIME content for email",
       "url": "https://developers.salesloft.com/docs/api/mime-email-payloads-show"
     },
-    {
-      "name": "notes-index",
-      "displayName": "List notes",
-      "url": "https://developers.salesloft.com/docs/api/notes-index"
-    },
     {
       "name": "notes-create",
       "displayName": "Create a note",
       "url": "https://developers.salesloft.com/docs/api/notes-create"
     },
+    {
+      "name": "notes-destroy",
+      "displayName": "Delete a note",
+      "url": "https://developers.salesloft.com/docs/api/notes-destroy"
+    },
+    {
+      "name": "notes-index",
+      "displayName": "List notes",
+      "url": "https://developers.salesloft.com/docs/api/notes-index"
+    },
     {
       "name": "notes-show",
       "displayName": "Fetch a note",
@@ -420,11 +490,6 @@
       "displayName": "Update a note",
       "url": "https://developers.salesloft.com/docs/api/notes-update"
     },
-    {
-      "name": "notes-destroy",
-      "displayName": "Delete a note",
-      "url": "https://developers.salesloft.com/docs/api/notes-destroy"
-    },
     {
       "name": "ongoing-actions-create",
       "displayName": "Create an ongoing action",
@@ -440,16 +505,21 @@
       "displayName": "Update a pending email",
       "url": "https://developers.salesloft.com/docs/api/pending-emails-update"
     },
-    {
-      "name": "people-index",
-      "displayName": "List people",
-      "url": "https://developers.salesloft.com/docs/api/people-index"
-    },
     {
       "name": "people-create",
       "displayName": "Create a person",
       "url": "https://developers.salesloft.com/docs/api/people-create"
     },
+    {
+      "name": "people-destroy",
+      "displayName": "Delete a person",
+      "url": "https://developers.salesloft.com/docs/api/people-destroy"
+    },
+    {
+      "name": "people-index",
+      "displayName": "List people",
+      "url": "https://developers.salesloft.com/docs/api/people-index"
+    },
     {
       "name": "people-show",
       "displayName": "Fetch a person",
@@ -461,25 +531,20 @@
       "url": "https://developers.salesloft.com/docs/api/people-update"
     },
     {
-      "name": "people-destroy",
-      "displayName": "Delete a person",
-      "url": "https://developers.salesloft.com/docs/api/people-destroy"
+      "name": "person-stages-create",
+      "displayName": "Create a person stage",
+      "url": "https://developers.salesloft.com/docs/api/person-stages-create"
     },
     {
-      "name": "data-control-people-soft-deletion-create",
-      "displayName": "Submit a people soft deletion request",
-      "url": "https://developers.salesloft.com/docs/api/data-control-people-soft-deletion-create"
+      "name": "person-stages-destroy",
+      "displayName": "Delete an person stage",
+      "url": "https://developers.salesloft.com/docs/api/person-stages-destroy"
     },
     {
       "name": "person-stages-index",
       "displayName": "List person stages",
       "url": "https://developers.salesloft.com/docs/api/person-stages-index"
     },
-    {
-      "name": "person-stages-create",
-      "displayName": "Create a person stage",
-      "url": "https://developers.salesloft.com/docs/api/person-stages-create"
-    },
     {
       "name": "person-stages-show",
       "displayName": "Fetch a person stage",
@@ -490,11 +555,6 @@
       "displayName": "Update a person stage",
       "url": "https://developers.salesloft.com/docs/api/person-stages-update"
     },
-    {
-      "name": "person-stages-destroy",
-      "displayName": "Delete an person stage",
-      "url": "https://developers.salesloft.com/docs/api/person-stages-destroy"
-    },
     {
       "name": "person-upserts-create",
       "displayName": "Upsert a person",
@@ -511,29 +571,9 @@
       "url": "https://developers.salesloft.com/docs/api/phone-number-assignments-show"
     },
     {
-      "name": "integrations-signals-registrations-plays-update",
-      "displayName": "Update a Play Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-update"
-    },
-    {
-      "name": "integrations-signals-registrations-plays-show",
-      "displayName": "Show a Play Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-show"
-    },
-    {
-      "name": "integrations-signals-registrations-plays-destroy",
-      "displayName": "Delete a Play Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-destroy"
-    },
-    {
-      "name": "integrations-signals-registrations-plays-create",
-      "displayName": "Create a Play Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-create"
-    },
-    {
-      "name": "integrations-signals-registrations-plays-index",
-      "displayName": "List Play Registrations",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-plays-index"
+      "name": "phone-numbers-caller-ids-index",
+      "displayName": "List caller ids",
+      "url": "https://developers.salesloft.com/docs/api/phone-numbers-caller-ids-index"
     },
     {
       "name": "phone-numbers-recording-settings-show",
@@ -541,85 +581,30 @@
       "url": "https://developers.salesloft.com/docs/api/phone-numbers-recording-settings-show"
     },
     {
-      "name": "data-control-requests-show",
-      "displayName": "Fetch a Request record",
-      "url": "https://developers.salesloft.com/docs/api/data-control-requests-show"
-    },
-    {
-      "name": "data-control-requests-index",
-      "displayName": "Retrieve a list of Requests",
-      "url": "https://developers.salesloft.com/docs/api/data-control-requests-index"
-    },
-    {
-      "name": "meetings-reschedule-links-show",
-      "displayName": "Fetch a reschedule meeting link",
-      "url": "https://developers.salesloft.com/docs/api/meetings-reschedule-links-show"
+      "name": "saved-list-views-create",
+      "displayName": "Create a saved list view",
+      "url": "https://developers.salesloft.com/docs/api/saved-list-views-create"
     },
     {
-      "name": "data-control-right-to-be-forgotten-create",
-      "displayName": "Submit a right to be forgotten request",
-      "url": "https://developers.salesloft.com/docs/api/data-control-right-to-be-forgotten-create"
+      "name": "saved-list-views-destroy",
+      "displayName": "Delete a saved list view",
+      "url": "https://developers.salesloft.com/docs/api/saved-list-views-destroy"
     },
     {
       "name": "saved-list-views-index",
       "displayName": "List saved list views",
       "url": "https://developers.salesloft.com/docs/api/saved-list-views-index"
     },
-    {
-      "name": "saved-list-views-create",
-      "displayName": "Create a saved list view",
-      "url": "https://developers.salesloft.com/docs/api/saved-list-views-create"
-    },
     {
       "name": "saved-list-views-show",
       "displayName": "Fetch a saved list view",
       "url": "https://developers.salesloft.com/docs/api/saved-list-views-show"
     },
-    {
-      "name": "saved-list-views-destroy",
-      "displayName": "Delete a saved list view",
-      "url": "https://developers.salesloft.com/docs/api/saved-list-views-destroy"
-    },
     {
       "name": "saved-list-views-update",
       "displayName": "Update a saved list view",
       "url": "https://developers.salesloft.com/docs/api/saved-list-views-update"
     },
-    {
-      "name": "meetings-settings-searches-create",
-      "displayName": "List meeting settings",
-      "url": "https://developers.salesloft.com/docs/api/meetings-settings-searches-create"
-    },
-    {
-      "name": "meetings-settings-update",
-      "displayName": "Update a meeting setting",
-      "url": "https://developers.salesloft.com/docs/api/meetings-settings-update"
-    },
-    {
-      "name": "integrations-signals-registrations-update",
-      "displayName": "Update a Signal Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-update"
-    },
-    {
-      "name": "integrations-signals-registrations-show",
-      "displayName": "Fetch a Signal Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-show"
-    },
-    {
-      "name": "integrations-signals-registrations-destroy",
-      "displayName": "Deletes a Signal Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-destroy"
-    },
-    {
-      "name": "integrations-signals-registrations-create",
-      "displayName": "Create a Signal Registration",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-create"
-    },
-    {
-      "name": "integrations-signals-registrations-index",
-      "displayName": "List Signal Registrations",
-      "url": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-index"
-    },
     {
       "name": "signals-create",
       "displayName": "Create a Signal",
@@ -645,6 +630,11 @@
       "displayName": "List team tags",
       "url": "https://developers.salesloft.com/docs/api/tags-index"
     },
+    {
+      "name": "tasks-counts-index",
+      "displayName": "Fetch task counts",
+      "url": "https://developers.salesloft.com/docs/api/tasks-counts-index"
+    },
     {
       "name": "tasks-create",
       "displayName": "Create a Task",
@@ -665,6 +655,11 @@
       "displayName": "Update a Task",
       "url": "https://developers.salesloft.com/docs/api/tasks-update"
     },
+    {
+      "name": "team-index",
+      "displayName": "Fetch current team",
+      "url": "https://developers.salesloft.com/docs/api/team-index"
+    },
     {
       "name": "team-template-attachments-index",
       "displayName": "List team template attachments",
@@ -680,6 +675,11 @@
       "displayName": "Fetch a team template",
       "url": "https://developers.salesloft.com/docs/api/team-templates-show"
     },
+    {
+      "name": "third-party-live-feed-items-create",
+      "displayName": "Create a live feed item",
+      "url": "https://developers.salesloft.com/docs/api/third-party-live-feed-items-create"
+    },
     {
       "name": "users-index",
       "displayName": "List users",
@@ -696,29 +696,29 @@
       "url": "https://developers.salesloft.com/docs/api/users-update"
     },
     {
-      "name": "webhook-subscriptions-update",
-      "displayName": "Update a webhook subscription",
-      "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-update"
-    },
-    {
-      "name": "webhook-subscriptions-show",
-      "displayName": "Fetch a webhook subscription",
-      "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-show"
+      "name": "webhook-subscriptions-create",
+      "displayName": "Create a webhook subscription",
+      "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-create"
     },
     {
       "name": "webhook-subscriptions-destroy",
       "displayName": "Delete a webhook subscription",
       "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-destroy"
     },
-    {
-      "name": "webhook-subscriptions-create",
-      "displayName": "Create a webhook subscription",
-      "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-create"
-    },
     {
       "name": "webhook-subscriptions-index",
       "displayName": "List webhook subscriptions",
       "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-index"
+    },
+    {
+      "name": "webhook-subscriptions-show",
+      "displayName": "Fetch a webhook subscription",
+      "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-show"
+    },
+    {
+      "name": "webhook-subscriptions-update",
+      "displayName": "Update a webhook subscription",
+      "url": "https://developers.salesloft.com/docs/api/webhook-subscriptions-update"
     }
   ]
 }
\ No newline at end of file
diff --git a/salesloft/metadata/schemas.json b/salesloft/metadata/schemas.json
index ce3c12ee..b69e2ff1 100755
--- a/salesloft/metadata/schemas.json
+++ b/salesloft/metadata/schemas.json
@@ -1,6 +1,6 @@
 {
   "data": {
-    "account-stages": {
+    "account_stages": {
       "displayName": "List account stages",
       "fields": {
         "active": "active",
@@ -11,7 +11,7 @@
         "updated_at": "updated_at"
       }
     },
-    "account-tiers": {
+    "account_tiers": {
       "displayName": "List Account Tiers",
       "fields": {
         "created_at": "created_at",
@@ -66,7 +66,7 @@
         "website": "website"
       }
     },
-    "action-details-call-instructions": {
+    "action_details_call_instructions": {
       "displayName": "List call instructions",
       "fields": {
         "created_at": "created_at",
@@ -94,7 +94,7 @@
         "user": "user"
       }
     },
-    "activities-calls": {
+    "activities_calls": {
       "displayName": "List calls",
       "fields": {
         "action": "action",
@@ -115,7 +115,7 @@
         "user": "user"
       }
     },
-    "activities-emails": {
+    "activities_emails": {
       "displayName": "List emails",
       "fields": {
         "action": "action",
@@ -127,6 +127,7 @@
         "counts": "counts",
         "created_at": "created_at",
         "crm_activity": "crm_activity",
+        "draft_with_ai": "draft_with_ai",
         "email_template": "email_template",
         "error_message": "error_message",
         "headers": "headers",
@@ -146,7 +147,7 @@
         "view_tracking": "view_tracking"
       }
     },
-    "activity-histories": {
+    "activity_histories": {
       "displayName": "List Past Activities",
       "fields": {
         "created_at": "created_at",
@@ -164,7 +165,7 @@
         "user_guid": "user_guid"
       }
     },
-    "bulk-jobs": {
+    "bulk_jobs": {
       "displayName": "List bulk jobs",
       "fields": {
         "created_at": "created_at",
@@ -183,7 +184,7 @@
         "updated_at": "updated_at"
       }
     },
-    "bulk-jobs-job-data": {
+    "bulk_jobs_job_data": {
       "displayName": "List job data for a bulk job",
       "fields": {
         "error": "error",
@@ -193,7 +194,7 @@
         "status": "status"
       }
     },
-    "bulk-jobs-results": {
+    "bulk_jobs_results": {
       "displayName": "List job data for a completed bulk job.",
       "fields": {
         "error": "error",
@@ -203,7 +204,7 @@
         "status": "status"
       }
     },
-    "cadence-memberships": {
+    "cadence_memberships": {
       "displayName": "List cadence memberships",
       "fields": {
         "added_at": "added_at",
@@ -252,7 +253,7 @@
         "updated_at": "updated_at"
       }
     },
-    "calendar-events": {
+    "calendar_events": {
       "displayName": "List calendar events",
       "fields": {
         "all_day": "all_day",
@@ -285,7 +286,7 @@
         "user_guid": "user_guid"
       }
     },
-    "call-data-records": {
+    "call_data_records": {
       "displayName": "List call data records",
       "fields": {
         "call": "call",
@@ -307,7 +308,7 @@
         "user": "user"
       }
     },
-    "call-dispositions": {
+    "call_dispositions": {
       "displayName": "List call dispositions",
       "fields": {
         "created_at": "created_at",
@@ -316,7 +317,7 @@
         "updated_at": "updated_at"
       }
     },
-    "call-sentiments": {
+    "call_sentiments": {
       "displayName": "List call sentiments",
       "fields": {
         "created_at": "created_at",
@@ -325,7 +326,7 @@
         "updated_at": "updated_at"
       }
     },
-    "crm-activities": {
+    "crm_activities": {
       "displayName": "List crm activities",
       "fields": {
         "activity_type": "activity_type",
@@ -341,7 +342,7 @@
         "user": "user"
       }
     },
-    "crm-activity-fields": {
+    "crm_activity_fields": {
       "displayName": "List crm activity fields",
       "fields": {
         "created_at": "created_at",
@@ -357,7 +358,7 @@
         "value": "value"
       }
     },
-    "crm-users": {
+    "crm_users": {
       "displayName": "List crm users",
       "fields": {
         "created_at": "created_at",
@@ -370,7 +371,7 @@
         "user": "user"
       }
     },
-    "custom-fields": {
+    "custom_fields": {
       "displayName": "List custom fields",
       "fields": {
         "created_at": "created_at",
@@ -381,14 +382,14 @@
         "value_type": "value_type"
       }
     },
-    "custom-roles": {
+    "custom_roles": {
       "displayName": "List custom roles",
       "fields": {
         "id": "id",
         "name": "name"
       }
     },
-    "data-control-requests": {
+    "data_control_requests": {
       "displayName": "Retrieve a list of Requests",
       "fields": {
         "dry_run": "dry_run",
@@ -400,7 +401,7 @@
         "user_guid": "user_guid"
       }
     },
-    "email-template-attachments": {
+    "email_template_attachments": {
       "displayName": "List email template attachments",
       "fields": {
         "attachment_content_type": "attachment_content_type",
@@ -414,7 +415,7 @@
         "scanned": "scanned"
       }
     },
-    "email-templates": {
+    "email_templates": {
       "displayName": "List email templates",
       "fields": {
         "_links": "_links",
@@ -461,7 +462,7 @@
         "user_id": "user_id"
       }
     },
-    "integrations-signals-registrations": {
+    "integrations_signals_registrations": {
       "displayName": "List Signal Registrations",
       "fields": {
         "attribution": "attribution",
@@ -477,7 +478,7 @@
         "type": "type"
       }
     },
-    "integrations-signals-registrations-plays": {
+    "integrations_signals_registrations_plays": {
       "displayName": "List Play Registrations",
       "fields": {
         "attributes": "attributes",
@@ -522,6 +523,7 @@
         "last_name": "last_name",
         "local_dial_enabled": "local_dial_enabled",
         "locale_utc_offset": "locale_utc_offset",
+        "manager_user_guid": "manager_user_guid",
         "name": "name",
         "phone_client": "phone_client",
         "phone_number_assignment": "phone_number_assignment",
@@ -597,7 +599,7 @@
         "user": "user"
       }
     },
-    "pending-emails": {
+    "pending_emails": {
       "displayName": "List pending emails",
       "fields": {
         "id": "id",
@@ -667,7 +669,7 @@
         "work_state": "work_state"
       }
     },
-    "person-stages": {
+    "person_stages": {
       "displayName": "List person stages",
       "fields": {
         "active": "active",
@@ -678,7 +680,7 @@
         "updated_at": "updated_at"
       }
     },
-    "phone-number-assignments": {
+    "phone_number_assignments": {
       "displayName": "List phone number assignments",
       "fields": {
         "id": "id",
@@ -686,7 +688,7 @@
         "user": "user"
       }
     },
-    "phone-numbers-caller-ids": {
+    "phone_numbers_caller_ids": {
       "displayName": "List caller ids",
       "fields": {
         "account_name": "account_name",
@@ -695,7 +697,7 @@
         "title": "title"
       }
     },
-    "saved-list-views": {
+    "saved_list_views": {
       "displayName": "List saved list views",
       "fields": {
         "id": "id",
@@ -769,13 +771,14 @@
         "user": "user"
       }
     },
-    "tasks-counts": {
+    "tasks_counts": {
       "displayName": "Fetch task counts",
       "fields": {
         "call": "call",
         "email": "email",
         "general": "general",
         "integration": "integration",
+        "other": "other",
         "total": "total"
       }
     },
@@ -803,7 +806,7 @@
         "updated_at": "updated_at"
       }
     },
-    "team-template-attachments": {
+    "team_template_attachments": {
       "displayName": "List team template attachments",
       "fields": {
         "attachment_file_size": "attachment_file_size",
@@ -814,7 +817,7 @@
         "team_template": "team_template"
       }
     },
-    "team-templates": {
+    "team_templates": {
       "displayName": "List team templates",
       "fields": {
         "_links": "_links",
@@ -860,6 +863,7 @@
         "last_name": "last_name",
         "local_dial_enabled": "local_dial_enabled",
         "locale_utc_offset": "locale_utc_offset",
+        "manager_user_guid": "manager_user_guid",
         "name": "name",
         "phone_client": "phone_client",
         "phone_number_assignment": "phone_number_assignment",
@@ -874,7 +878,7 @@
         "work_country": "work_country"
       }
     },
-    "webhook-subscriptions": {
+    "webhook_subscriptions": {
       "displayName": "List webhook subscriptions",
       "fields": {
         "callback_token": "callback_token",
diff --git a/salesloft/params.go b/salesloft/params.go
index 4069b009..a556b985 100644
--- a/salesloft/params.go
+++ b/salesloft/params.go
@@ -21,7 +21,6 @@ type Option func(params *parameters)
 // parameters Salesloft supports auth client, workspace, etc. by delegation.
 type parameters struct {
 	paramsbuilder.Client
-	paramsbuilder.Module
 }
 
 func (p parameters) FromOptions(opts ...Option) (*parameters, error) {
@@ -36,7 +35,6 @@ func (p parameters) FromOptions(opts ...Option) (*parameters, error) {
 func (p parameters) ValidateParams() error {
 	return errors.Join(
 		p.Client.ValidateParams(),
-		p.Module.ValidateParams(),
 	)
 }
 
@@ -53,9 +51,3 @@ func WithAuthenticatedClient(client common.AuthenticatedHTTPClient) Option {
 		params.WithAuthenticatedClient(client)
 	}
 }
-
-func WithModule(module paramsbuilder.APIModule) Option {
-	return func(params *parameters) {
-		params.WithModule(module)
-	}
-}
diff --git a/scripts/bash/pr_options.sh b/scripts/bash/pr_options.sh
index abb5e0d7..de730bc6 100755
--- a/scripts/bash/pr_options.sh
+++ b/scripts/bash/pr_options.sh
@@ -8,7 +8,9 @@ pr_template() {
   TARGET_BRANCH="main"
 
   USER_NAME=$(git config user.name)
-  REPO_NAME=$(basename -s .git `git config --get remote.origin.url`) # your repo name, can be fork name
+  URL=$(git config --get remote.origin.url)
+  REPO_NAME=$(basename -s .git "$URL") # your repo name, can be fork name
+  ORGANIZATION="$(echo "$URL" | sed -r 's/(.+):(.+)\/([^.]+)(\.git)?/\2/')"
 
   echo "PR templates"
   # For every template markdown file construct a URL
@@ -16,9 +18,11 @@ pr_template() {
   do
     TEMPLATE=$(basename "$FILE_NAME")
     # Construct URL for comparing branch against main origin
-    PR_URL="https://github.com/amp-labs/connectors/compare/${TARGET_BRANCH}...${USER_NAME}:${REPO_NAME}:${SOURCE_BRANCH}?template=${TEMPLATE}"
+    PR_URL="https://github.com/amp-labs/connectors/compare/${TARGET_BRANCH}...${ORGANIZATION}:${REPO_NAME}:${SOURCE_BRANCH}?template=${TEMPLATE}"
+    PR_FORK_URL="https://github.com/amp-labs/connectors/compare/${TARGET_BRANCH}...${USER_NAME}:${REPO_NAME}:${SOURCE_BRANCH}?template=${TEMPLATE}"
 
-    # Display 2 columns with ident, where first column is min 40 chars
-    printf "\t %-40s %--s\n" "${TEMPLATE}:" "$PR_URL"
+    # Display 3 columns with ident, where first column is min 40 chars
+    printf "\t %-40s %-6s %--s\n" "${TEMPLATE}:" "local:" "$PR_URL"
+    printf "\t %-40s %-6s %--s\n" "" "fork:" "$PR_FORK_URL"
   done
 }
diff --git a/scripts/oauth/token.go b/scripts/oauth/token.go
index 06f9f519..905649ce 100644
--- a/scripts/oauth/token.go
+++ b/scripts/oauth/token.go
@@ -2,6 +2,7 @@
 package main
 
 import (
+	"context"
 	"encoding/base64"
 	"encoding/json"
 	"flag"
@@ -9,6 +10,7 @@ import (
 	"log"
 	"log/slog"
 	"net/http"
+	"net/url"
 	"os"
 	"os/exec"
 	"runtime"
@@ -19,6 +21,7 @@ import (
 	"github.com/amp-labs/connectors/providers"
 	"github.com/amp-labs/connectors/utils"
 	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/clientcredentials"
 )
 
 // ================================
@@ -96,14 +99,16 @@ var readers = []utils.Reader{
 
 // OAuthApp is a simple OAuth app that can be used to get an OAuth token.
 type OAuthApp struct {
-	Callback string
-	Port     int
-	Config   *oauth2.Config
-	Options  []oauth2.AuthCodeOption
-	State    string
-	Proto    string
-	SSLCert  string
-	SSLKey   string
+	GrantType         providers.OauthOptsGrantType
+	Callback          string
+	Port              int
+	Config            *oauth2.Config
+	ClientCredsConfig *clientcredentials.Config
+	Options           []oauth2.AuthCodeOption
+	State             string
+	Proto             string
+	SSLCert           string
+	SSLKey            string
 }
 
 // ServeHTTP implements the http.Handler interface.
@@ -193,25 +198,39 @@ func (a *OAuthApp) processCallback(writer http.ResponseWriter, request *http.Req
 
 // Run executes the OAuth flow to get a token.
 func (a *OAuthApp) Run() error {
-	slog.Info("starting OAuth app", "port", a.Port)
+	if a.GrantType == providers.ClientCredentials {
+		src := a.ClientCredsConfig.TokenSource(context.Background())
+		tok, err := src.Token()
+		if err != nil {
+			return err
+		}
 
-	http.Handle("/", a)
+		header := tok.Type() + " " + tok.AccessToken
+		fmt.Println("Expiry: " + tok.Expiry.String())
+		fmt.Println("Authorization: " + header)
 
-	go func() {
-		time.Sleep(1 * time.Second)
-		openBrowser(fmt.Sprintf("%s://localhost:%d", a.Proto, a.Port))
-	}()
+		return nil
+	} else {
+		slog.Info("starting OAuth app", "port", a.Port)
 
-	server := &http.Server{
-		Addr:              fmt.Sprintf("0.0.0.0:%d", a.Port),
-		ReadHeaderTimeout: ReadHeaderTimeoutSeconds * time.Second,
-	}
+		http.Handle("/", a)
 
-	if a.Proto == HttpProtocol {
-		// nosemgrep: go.lang.security.audit.net.use-tls.use-tls
-		return server.ListenAndServe()
-	} else {
-		return server.ListenAndServeTLS(a.SSLCert, a.SSLKey)
+		go func() {
+			time.Sleep(1 * time.Second)
+			openBrowser(fmt.Sprintf("%s://localhost:%d", a.Proto, a.Port))
+		}()
+
+		server := &http.Server{
+			Addr:              fmt.Sprintf("0.0.0.0:%d", a.Port),
+			ReadHeaderTimeout: ReadHeaderTimeoutSeconds * time.Second,
+		}
+
+		if a.Proto == HttpProtocol {
+			// nosemgrep: go.lang.security.audit.net.use-tls.use-tls
+			return server.ListenAndServe()
+		} else {
+			return server.ListenAndServeTLS(a.SSLCert, a.SSLKey)
+		}
 	}
 }
 
@@ -248,24 +267,46 @@ func setup() *OAuthApp {
 	callback := flag.String("callback", DefaultCallbackPath, "the full OAuth callback path (arbitrary)")
 	flag.Parse()
 
-	err := registry.AddReaders(readers...)
+	substitutions, err := registry.GetMap("Substitutions")
 	if err != nil {
-		return nil
+		slog.Warn("no substitutions, ensure that the provider info doesn't have any {{variables}}")
+	}
+
+	// Cast the substitutions to a map[string]string
+	substitutionsMap := make(map[string]string)
+	for key, val := range substitutions {
+		substitutionsMap[key] = val.MustString()
 	}
 
-	// Determine the OAuth redirect URL.
-	redirect := fmt.Sprintf("%s://localhost:%d%s", *proto, *port, *callback)
+	if err = registry.AddReaders(readers...); err != nil {
+		return nil
+	}
 
-	// Get the OAuth scopes from the flag.
 	provider := registry.MustString("Provider")
-	clientId := registry.MustString("ClientId")
-	clientSecret := registry.MustString("ClientSecret")
 
-	state, err := registry.GetString("State")
+	providerInfo, err := providers.ReadInfo(provider, &substitutionsMap)
 	if err != nil {
-		slog.Warn("no state attached, ensure that the provider doesn't require state")
+		slog.Error("failed to read provider config", "error", err)
+
+		os.Exit(1)
+	}
+
+	if providerInfo.AuthType != providers.Oauth2 {
+		slog.Error("provider does not support OAuth2, not compatible with this script", "provider", provider)
+
+		os.Exit(1)
+	}
+
+	if providerInfo.OauthOpts == nil {
+		slog.Error("provider does not have OAuth2 options, not compatible with this script", "provider", provider)
+
+		os.Exit(1)
 	}
 
+	// Get the OAuth scopes from the flag.
+	clientId := registry.MustString("ClientId")
+	clientSecret := registry.MustString("ClientSecret")
+
 	scopes, err := registry.GetString("Scopes")
 	if err != nil {
 		slog.Warn("no scopes attached, ensure that the provider doesn't require scopes")
@@ -273,50 +314,83 @@ func setup() *OAuthApp {
 
 	oauthScopes := strings.Split(scopes, ",")
 
-	// Create the OAuth app.
-	app := &OAuthApp{
-		Callback: *callback,
-		Port:     *port,
-		Proto:    *proto,
-		SSLCert:  *SSLCert,
-		SSLKey:   *SSLKey,
-		Config: &oauth2.Config{
-			ClientID:     clientId,
-			ClientSecret: clientSecret,
-			RedirectURL:  redirect,
-			Scopes:       oauthScopes,
-		},
-	}
-	if state != "" {
-		app.State = state
-	}
+	switch providerInfo.OauthOpts.GrantType {
+	case providers.AuthorizationCode:
+		if providerInfo.OauthOpts.AuthURL == "" {
+			slog.Error("provider does not have an AuthURL, not compatible with this script", "provider", provider)
 
-	substitutions, err := registry.GetMap("Substitutions")
-	if err != nil {
-		slog.Warn("no substitutions, ensure that the provider info doesn't have any {{variables}}")
-	}
+			os.Exit(1)
+		}
 
-	// Cast the substitutions to a map[string]string
-	substitutionsMap := make(map[string]string)
-	for key, val := range substitutions {
-		substitutionsMap[key] = val.MustString()
-	}
+		// Determine the OAuth redirect URL.
+		redirect := fmt.Sprintf("%s://localhost:%d%s", *proto, *port, *callback)
 
-	providerInfo, err := providers.ReadInfo(provider, &substitutionsMap)
-	if err != nil {
-		slog.Error("failed to read provider config", "error", err)
+		state, err := registry.GetString("State")
+		if err != nil {
+			slog.Warn("no state attached, ensure that the provider doesn't require state")
+		}
 
-		os.Exit(1)
-	}
+		// Create the OAuth app.
+		app := &OAuthApp{
+			GrantType: providers.AuthorizationCode,
+			Callback:  *callback,
+			Port:      *port,
+			Proto:     *proto,
+			SSLCert:   *SSLCert,
+			SSLKey:    *SSLKey,
+			Config: &oauth2.Config{
+				ClientID:     clientId,
+				ClientSecret: clientSecret,
+				RedirectURL:  redirect,
+				Scopes:       oauthScopes,
+			},
+		}
+		if state != "" {
+			app.State = state
+		}
+
+		// Set up the OAuth config based on the provider.
+		app.Config.Endpoint = oauth2.Endpoint{
+			AuthURL:   providerInfo.OauthOpts.AuthURL,
+			TokenURL:  providerInfo.OauthOpts.TokenURL,
+			AuthStyle: oauth2.AuthStyleAutoDetect,
+		}
 
-	// Set up the OAuth config based on the provider.
-	app.Config.Endpoint = oauth2.Endpoint{
-		AuthURL:   providerInfo.OauthOpts.AuthURL,
-		TokenURL:  providerInfo.OauthOpts.TokenURL,
-		AuthStyle: oauth2.AuthStyleAutoDetect,
+		return app
+	case providers.ClientCredentials:
+		state, err := registry.GetString("State")
+		if err != nil {
+			slog.Warn("no state attached, ensure that the provider doesn't require state")
+		}
+
+		// Create the OAuth app.
+		app := &OAuthApp{
+			GrantType: providers.ClientCredentials,
+			ClientCredsConfig: &clientcredentials.Config{
+				ClientID:     clientId,
+				ClientSecret: clientSecret,
+				TokenURL:     providerInfo.OauthOpts.TokenURL,
+				Scopes:       oauthScopes,
+				AuthStyle:    oauth2.AuthStyleAutoDetect,
+			},
+		}
+		if state != "" {
+			app.State = state
+		}
+
+		if providerInfo.OauthOpts.Audience != "" {
+			aud := providerInfo.OauthOpts.Audience
+			app.ClientCredsConfig.EndpointParams = url.Values{"audience": {aud}}
+		}
+
+		return app
+	default:
+		slog.Error("provider does not support authorization code or client credentials grant, not compatible with this script", "provider", provider)
+
+		os.Exit(1)
 	}
 
-	return app
+	return nil
 }
 
 func main() {
diff --git a/scripts/proxy/proxy.go b/scripts/proxy/proxy.go
index 8b6a4d03..8c36137b 100644
--- a/scripts/proxy/proxy.go
+++ b/scripts/proxy/proxy.go
@@ -236,7 +236,6 @@ func mainBasic(ctx context.Context, provider string, substitutionsMap map[string
 func getTokensFromRegistry() *oauth2.Token {
 	accessToken := registry.MustString("AccessToken")
 	refreshToken, err := registry.GetString("RefreshToken")
-
 	if err != nil {
 		// we are working without refresh token
 		return &oauth2.Token{
@@ -434,12 +433,19 @@ func getProviderConfig(provider string, substitutions map[string]string) *provid
 }
 
 func configureOAuthClientCredentials(clientId, clientSecret string, scopes []string, providerInfo *providers.ProviderInfo) *clientcredentials.Config {
-	return &clientcredentials.Config{
+	cfg := &clientcredentials.Config{
 		ClientID:     clientId,
 		ClientSecret: clientSecret,
 		Scopes:       scopes,
 		TokenURL:     providerInfo.OauthOpts.TokenURL,
 	}
+
+	if providerInfo.OauthOpts.Audience != "" {
+		aud := providerInfo.OauthOpts.Audience
+		cfg.EndpointParams = url.Values{"audience": {aud}}
+	}
+
+	return cfg
 }
 
 func configureOAuthAuthCode(clientId, clientSecret string, scopes []string, providerInfo *providers.ProviderInfo) *oauth2.Config {
diff --git a/scripts/scrapper/salesloft/metadata/main.go b/scripts/scrapper/salesloft/metadata/main.go
index 720f1acd..aab96f77 100644
--- a/scripts/scrapper/salesloft/metadata/main.go
+++ b/scripts/scrapper/salesloft/metadata/main.go
@@ -7,6 +7,7 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/amp-labs/connectors/salesloft/metadata"
 	"github.com/amp-labs/connectors/tools/scrapper"
+	"github.com/iancoleman/strcase"
 )
 
 const (
@@ -56,6 +57,8 @@ func createSchemas() {
 		doc := scrapper.QueryHTML(model.URL)
 
 		// There are 2 unordered lists that describe response schema
+		modelName := strcase.ToSnake(model.Name)
+
 		doc.Find(`.openapi-tabs__schema-container ul`).
 			Each(func(i int, list *goquery.Selection) {
 				list.Children().Each(func(i int, property *goquery.Selection) {
@@ -63,12 +66,12 @@ func createSchemas() {
 					// Only the first most field represents top level fields of response payload
 					fieldName := property.Find(`strong`).First().Text()
 					if len(fieldName) != 0 {
-						schemas.Add(model.Name, model.DisplayName, fieldName)
+						schemas.Add(modelName, model.DisplayName, fieldName)
 					}
 				})
 			})
 
-		log.Printf("Schemas completed %.2f%% [%v]\n", getPercentage(i, len(filteredListDocs)), model.Name)
+		log.Printf("Schemas completed %.2f%% [%v]\n", getPercentage(i, len(filteredListDocs)), modelName)
 	}
 
 	must(metadata.FileManager.SaveSchemas(schemas))
diff --git a/tools/scrapper/files.go b/tools/scrapper/files.go
index 5396388e..594efe02 100644
--- a/tools/scrapper/files.go
+++ b/tools/scrapper/files.go
@@ -26,6 +26,8 @@ func NewMetadataFileManager(schemas []byte, locator MetadataFileLocator) *Metada
 }
 
 func (m MetadataFileManager) SaveIndex(index *ModelURLRegistry) error {
+	index.Sort()
+
 	return FlushToFile(m.locator.AbsPathTo(IndexFile), index)
 }
 
diff --git a/tools/scrapper/models.go b/tools/scrapper/models.go
index bde481cb..da9a9618 100644
--- a/tools/scrapper/models.go
+++ b/tools/scrapper/models.go
@@ -1,6 +1,7 @@
 package scrapper
 
 import (
+	"sort"
 	"strings"
 )
 
@@ -49,6 +50,12 @@ func (r *ModelURLRegistry) Add(displayName, url string) {
 	})
 }
 
+func (r *ModelURLRegistry) Sort() {
+	sort.Slice(r.ModelDocs, func(i, j int) bool {
+		return r.ModelDocs[i].Name < r.ModelDocs[j].Name
+	})
+}
+
 type ObjectMetadataResult struct {
 	// Result is a map of object names to object metadata
 	Result map[string]ObjectMetadata `json:"data"`


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/salesloft/metadata_test.go b/salesloft/metadata_test.go
index 456dd5f4..e1d91a77 100644
--- a/salesloft/metadata_test.go
+++ b/salesloft/metadata_test.go
@@ -42,13 +42,13 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 		},
 		{
 			name:  "Successfully describe one object with metadata",
-			input: []string{"bulk-jobs-results"},
+			input: []string{"bulk_jobs_results"},
 			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.WriteHeader(http.StatusTeapot)
 			})),
 			expected: &common.ListObjectMetadataResult{
 				Result: map[string]common.ObjectMetadata{
-					"bulk-jobs-results": {
+					"bulk_jobs_results": {
 						DisplayName: "List job data for a completed bulk job.",
 						FieldsMap: map[string]string{
 							"error":    "error",
@@ -65,13 +65,13 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 		},
 		{
 			name:  "Successfully describe multiple objects with metadata",
-			input: []string{"account-tiers", "actions"},
+			input: []string{"account_tiers", "actions"},
 			server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				w.WriteHeader(http.StatusTeapot)
 			})),
 			expected: &common.ListObjectMetadataResult{
 				Result: map[string]common.ObjectMetadata{
-					"account-tiers": {
+					"account_tiers": {
 						DisplayName: "List Account Tiers",
 						FieldsMap: map[string]string{
 							"created_at": "created_at",
diff --git a/salesloft/read_test.go b/salesloft/read_test.go
index 0a11ad22..8f36650a 100644
--- a/salesloft/read_test.go
+++ b/salesloft/read_test.go
@@ -99,7 +99,7 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 				return actual.NextPage.String() == expectedNextPage // nolint:nlreturn
 			},
 			expected: &common.ReadResult{
-				NextPage: "{{testServerURL}}?page=2&per_page=100",
+				NextPage: "{{testServerURL}}/v2?page=2&per_page=100",
 			},
 			expectedErrs: nil,
 		},
diff --git a/test/dynamicscrm/connector.go b/test/dynamicscrm/connector.go
index 241b92b2..aa05a608 100644
--- a/test/dynamicscrm/connector.go
+++ b/test/dynamicscrm/connector.go
@@ -48,7 +48,6 @@ func GetMSDynamics365CRMConnector(ctx context.Context, filePath string) *dynamic
 	conn, err := connectors.DynamicsCRM(
 		dynamicscrm.WithClient(ctx, http.DefaultClient, cfg, tok),
 		dynamicscrm.WithWorkspace(utils.MSDynamics365CRMWorkspace),
-		dynamicscrm.WithModule(dynamicscrm.DataModule),
 	)
 	if err != nil {
 		testUtils.Fail("error creating microsoft CRM connector", "error", err)
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/salesloft/connector.go b/test/salesloft/connector.go
index e1dc9edc..bbae2411 100644
--- a/test/salesloft/connector.go
+++ b/test/salesloft/connector.go
@@ -47,7 +47,6 @@ func GetSalesloftConnector(ctx context.Context, filePath string) *salesloft.Conn
 
 	conn, err := connectors.Salesloft(
 		salesloft.WithClient(ctx, http.DefaultClient, cfg, tok),
-		salesloft.WithModule(salesloft.DefaultModuleCRM),
 	)
 	if err != nil {
 		testUtils.Fail("error creating Salesloft connector", "error", err)
diff --git a/test/utils/mockutils/server.go b/test/utils/mockutils/server.go
index 8b45a00b..f546bcfc 100644
--- a/test/utils/mockutils/server.go
+++ b/test/utils/mockutils/server.go
@@ -22,11 +22,43 @@ 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
+}