Repository: amp-labs/connectors
Pull request #1490 (2025-03-17 22:20:17)

fix(Salesloft): Endpoint URLs
# Fix
* Corrected invalid URL endpoints.
* Check schema.json for the actual changes ([commit 1](https://github.com/amp-labs/connectors/pull/1490/commits/6f3e0c55e2f0d3d593fdfb5bc63fa9c85dd9306b)).
* Fixed URL lookup and enforced URL path validation in every mock test ([commit 2](https://github.com/amp-labs/connectors/pull/1490/commits/67d36990ec6aae98e2bcea94d1de0292936468c2)).

![image](https://github.com/user-attachments/assets/3956a803-afc7-419d-b94b-bbc54c25b236)


# Scraper
* Updated the scraper script to use a headless browser for HTML loading.
* The website has changed since the last update, now relying more on JavaScript to load relevant sections.

# FYI
* Salesloft was the only connector still using "locatedAt", which was always set to "data".
* The bug in array extraction didn’t surface because the Read implementation relied on a hardcoded "data" instead of querying the file. If it had, the issue would have been immediately noticeable.

Cobalt0s: * **#1491** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1491?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#1490** <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1490?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/amp-labs/connectors/1490?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `main`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
Cobalt0s: Double checked that Since argument is considered during read for `activities/calls`:

![image](https://github.com/user-attachments/assets/31f74b5d-0acf-42bd-8679-61ff72d869cd)


Patch:
diff --git a/go.mod b/go.mod
index 120e8d3f..f99dc6c5 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
 	github.com/PuerkitoBio/goquery v1.10.2
 	github.com/antchfx/xmlquery v1.4.3
 	github.com/brianvoe/gofakeit/v6 v6.28.0
+	github.com/chromedp/chromedp v0.13.1
 	github.com/deiu/linkparser v0.0.0-20170608193052-9b6849e15168
 	github.com/gertd/go-pluralize v0.2.1
 	github.com/getkin/kin-openapi v0.129.0
@@ -28,11 +29,17 @@ require (
 require (
 	github.com/andybalholm/cascadia v1.3.3 // indirect
 	github.com/antchfx/xpath v1.3.3 // indirect
+	github.com/chromedp/cdproto v0.0.0-20250222051814-50c6cb17f10a // indirect
+	github.com/chromedp/sysutil v1.1.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
+	github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect
 	github.com/go-openapi/jsonpointer v0.21.0 // indirect
 	github.com/go-openapi/swag v0.23.0 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/gobwas/httphead v0.1.0 // indirect
+	github.com/gobwas/pool v0.2.1 // indirect
+	github.com/gobwas/ws v1.4.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 611af918..bba3c264 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,12 @@ github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
 github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
 github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4=
 github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs=
+github.com/chromedp/cdproto v0.0.0-20250222051814-50c6cb17f10a h1:EnkQjhmp/MxhDB4KOTssv6xC20aQ9rhFRCfGHTsTqmE=
+github.com/chromedp/cdproto v0.0.0-20250222051814-50c6cb17f10a/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
+github.com/chromedp/chromedp v0.13.1 h1:FDh9CfaAt0w70gl69Hb69M/xgZrWuppH9AW22aGa+iU=
+github.com/chromedp/chromedp v0.13.1/go.mod h1:O3nO4Lno7iLoVX+7GdqQkehhKG7DtLf/zFRyJo0AhXY=
+github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
+github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -23,6 +29,8 @@ github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlL
 github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
 github.com/getkin/kin-openapi v0.129.0 h1:QGYTNcmyP5X0AtFQ2Dkou9DGBJsUETeLH9rFrJXZh30=
 github.com/getkin/kin-openapi v0.129.0/go.mod h1:gmWI+b/J45xqpyK5wJmRRZse5wefA5H0RDMK46kLUtI=
+github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=
+github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
 github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
 github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
@@ -37,6 +45,12 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
 github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
+github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
+github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
+github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
+github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
 github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
 github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
@@ -57,6 +71,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
+github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
@@ -71,6 +87,8 @@ github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 h1:nZspmSkneBbtxU9Top
 github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80/go.mod h1:7tFDb+Y51LcDpn26GccuUgQXUk6t0CXZsivKjyimYX8=
 github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 h1:t05Ww3DxZutOqbMN+7OIuqDwXbhl32HiZGpLy26BAPc=
 github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
+github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
+github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
 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/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
@@ -160,6 +178,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/internal/staticschema/files.go b/internal/staticschema/files.go
index de733f3b..44c7a003 100644
--- a/internal/staticschema/files.go
+++ b/internal/staticschema/files.go
@@ -26,6 +26,11 @@ func NewFileManager[F FieldMetadataMap, C any](schemas []byte, locator fileconv.
 func (m FileManager[F, C]) SaveSchemas(schemas *Metadata[F, C]) error {
 	schemas.refactorLongestCommonPath()
 
+	return m.FlushSchemas(schemas)
+}
+
+// FlushSchemas stores schemas into the file without any modifications.
+func (m FileManager[F, C]) FlushSchemas(schemas *Metadata[F, C]) error {
 	return m.flush.ToFile(m.locator.AbsPathTo(SchemasFile), schemas)
 }
 
diff --git a/providers/salesloft/connector.go b/providers/salesloft/connector.go
index 0b28624b..382e8411 100644
--- a/providers/salesloft/connector.go
+++ b/providers/salesloft/connector.go
@@ -6,9 +6,10 @@ import (
 	"github.com/amp-labs/connectors/common/paramsbuilder"
 	"github.com/amp-labs/connectors/common/urlbuilder"
 	"github.com/amp-labs/connectors/providers"
+	"github.com/amp-labs/connectors/providers/salesloft/metadata"
 )
 
-const apiVersion = "v2"
+const ApiVersion = "v2"
 
 type Connector struct {
 	BaseURL string
@@ -50,8 +51,14 @@ func (c *Connector) String() string {
 	return c.Provider() + ".Connector"
 }
 
-func (c *Connector) getURL(arg string) (*urlbuilder.URL, error) {
-	return urlbuilder.New(c.BaseURL, apiVersion, arg)
+func (c *Connector) getURL(objectName string) (*urlbuilder.URL, error) {
+	path, _ := metadata.Schemas.LookupURLPath(c.Module.ID, objectName)
+	if len(path) == 0 {
+		// Fallback, try objectName as a URL.
+		path = objectName
+	}
+
+	return urlbuilder.New(c.BaseURL, ApiVersion, path)
 }
 
 func (c *Connector) setBaseURL(newURL string) {
diff --git a/providers/salesloft/metadata/index.json b/providers/salesloft/metadata/index.json
index 6ef8c270..74d94adb 100755
--- a/providers/salesloft/metadata/index.json
+++ b/providers/salesloft/metadata/index.json
@@ -10,6 +10,11 @@
       "displayName": "Fetch an account stage",
       "url": "https://developers.salesloft.com/docs/api/account-stages-show"
     },
+    {
+      "name": "account-team-member-roles-index",
+      "displayName": "List CRM Account Team Member Roles",
+      "url": "https://developers.salesloft.com/docs/api/account-team-member-roles-index"
+    },
     {
       "name": "account-tiers-index",
       "displayName": "List Account Tiers",
@@ -120,16 +125,6 @@
       "displayName": "Create job data for a bulk job",
       "url": "https://developers.salesloft.com/docs/api/bulk-jobs-job-data-create"
     },
-    {
-      "name": "bulk-jobs-job-data-index",
-      "displayName": "List job data for a bulk job",
-      "url": "https://developers.salesloft.com/docs/api/bulk-jobs-job-data-index"
-    },
-    {
-      "name": "bulk-jobs-results-index",
-      "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",
@@ -607,17 +602,17 @@
     },
     {
       "name": "opportunity-people-create",
-      "displayName": "Create a Sales Opportunity Person",
+      "displayName": "Create an Opportunity and Person Association",
       "url": "https://developers.salesloft.com/docs/api/opportunity-people-create"
     },
     {
       "name": "opportunity-people-destroy",
-      "displayName": "Delete a Opportunity Association",
+      "displayName": "Delete an Opportunity and Person Association",
       "url": "https://developers.salesloft.com/docs/api/opportunity-people-destroy"
     },
     {
       "name": "opportunity-people-index",
-      "displayName": "List opportunity/person associations",
+      "displayName": "List Opportunity and Person Associations",
       "url": "https://developers.salesloft.com/docs/api/opportunity-people-index"
     },
     {
@@ -852,12 +847,12 @@
     },
     {
       "name": "users-show",
-      "displayName": "Fetch a user",
+      "displayName": "Fetch a user, by User ID or User GUID",
       "url": "https://developers.salesloft.com/docs/api/users-show"
     },
     {
       "name": "users-update",
-      "displayName": "Update a user",
+      "displayName": "Update a user, by User ID or User GUID",
       "url": "https://developers.salesloft.com/docs/api/users-update"
     },
     {
diff --git a/providers/salesloft/metadata/schemas.json b/providers/salesloft/metadata/schemas.json
index ee4ccdfb..9eadee7b 100755
--- a/providers/salesloft/metadata/schemas.json
+++ b/providers/salesloft/metadata/schemas.json
@@ -7,7 +7,7 @@
         "account_stages": {
           "displayName": "Account Stages",
           "path": "/account_stages",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "active": "active",
             "created_at": "created_at",
@@ -18,10 +18,20 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/account-stages-index"
         },
+        "account_team_member_roles": {
+          "displayName": "Crm Account Team Member Roles",
+          "path": "/account_team_member_roles",
+          "responseKey": "data",
+          "fields": {
+            "id": "id",
+            "name": "name"
+          },
+          "docs": "https://developers.salesloft.com/docs/api/account-team-member-roles-index"
+        },
         "account_tiers": {
           "displayName": "Account Tiers",
           "path": "/account_tiers",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "active": "active",
             "created_at": "created_at",
@@ -35,7 +45,7 @@
         "accounts": {
           "displayName": "Accounts",
           "path": "/accounts",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "account_tier": "account_tier",
             "archived_at": "archived_at",
@@ -80,10 +90,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/accounts-index"
         },
-        "action_details_call_instructions": {
+        "action_details/call_instructions": {
           "displayName": "Call Instructions",
-          "path": "/action_details_call_instructions",
-          "locatedAt": "data",
+          "path": "/action_details/call_instructions",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "id": "id",
@@ -95,7 +105,7 @@
         "actions": {
           "displayName": "Actions",
           "path": "/actions",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "action_details": "action_details",
             "cadence": "cadence",
@@ -114,20 +124,22 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/actions-index"
         },
-        "activities_calls": {
+        "activities/calls": {
           "displayName": "Calls",
-          "path": "/activities_calls",
-          "locatedAt": "data",
+          "path": "/activities/calls",
+          "responseKey": "data",
           "fields": {
             "action": "action",
             "cadence": "cadence",
             "called_person": "called_person",
+            "connected": "connected",
             "created_at": "created_at",
             "crm_activity": "crm_activity",
             "disposition": "disposition",
             "duration": "duration",
             "id": "id",
             "note": "note",
+            "positive": "positive",
             "recordings": "recordings",
             "sentiment": "sentiment",
             "step": "step",
@@ -138,10 +150,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/activities-calls-index"
         },
-        "activities_emails": {
+        "activities/emails": {
           "displayName": "Emails",
-          "path": "/activities_emails",
-          "locatedAt": "data",
+          "path": "/activities/emails",
+          "responseKey": "data",
           "fields": {
             "action": "action",
             "additional_recipients": "additional_recipients",
@@ -176,7 +188,7 @@
         "activity_histories": {
           "displayName": "Past Activities",
           "path": "/activity_histories",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "dynamic_data": "dynamic_data",
@@ -197,7 +209,7 @@
         "bulk_jobs": {
           "displayName": "Bulk Jobs",
           "path": "/bulk_jobs",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "errors": "errors",
@@ -216,36 +228,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/bulk-jobs-index"
         },
-        "bulk_jobs_job_data": {
-          "displayName": "Job Data for a Bulk Job",
-          "path": "/bulk_jobs_job_data",
-          "locatedAt": "data",
-          "fields": {
-            "error": "error",
-            "id": "id",
-            "record": "record",
-            "resource": "resource",
-            "status": "status"
-          },
-          "docs": "https://developers.salesloft.com/docs/api/bulk-jobs-job-data-index"
-        },
-        "bulk_jobs_results": {
-          "displayName": "Job Data for a Completed Bulk Job.",
-          "path": "/bulk_jobs_results",
-          "locatedAt": "data",
-          "fields": {
-            "error": "error",
-            "id": "id",
-            "record": "record",
-            "resource": "resource",
-            "status": "status"
-          },
-          "docs": "https://developers.salesloft.com/docs/api/bulk-jobs-results-index"
-        },
         "cadence_memberships": {
           "displayName": "Cadence Memberships",
           "path": "/cadence_memberships",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "added_at": "added_at",
             "cadence": "cadence",
@@ -265,7 +251,7 @@
         "cadences": {
           "displayName": "Cadences",
           "path": "/cadences",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "added_stage": "added_stage",
             "archived": "archived",
@@ -299,10 +285,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/cadences-index"
         },
-        "calendar_events": {
+        "calendar/events": {
           "displayName": "Calendar Events",
-          "path": "/calendar_events",
-          "locatedAt": "data",
+          "path": "/calendar/events",
+          "responseKey": "data",
           "fields": {
             "all_day": "all_day",
             "attendees": "attendees",
@@ -338,7 +324,7 @@
         "call_data_records": {
           "displayName": "Call Data Records",
           "path": "/call_data_records",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "call": "call",
             "call_type": "call_type",
@@ -363,7 +349,7 @@
         "call_dispositions": {
           "displayName": "Call Dispositions",
           "path": "/call_dispositions",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "id": "id",
@@ -375,7 +361,7 @@
         "call_sentiments": {
           "displayName": "Call Sentiments",
           "path": "/call_sentiments",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "id": "id",
@@ -387,7 +373,7 @@
         "crm_account_team_members": {
           "displayName": "Crm Account Team Members",
           "path": "/crm_account_team_members",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "account_access_level": "account_access_level",
             "account_crm_id": "account_crm_id",
@@ -402,7 +388,7 @@
         "crm_activities": {
           "displayName": "Crm Activities",
           "path": "/crm_activities",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "activity_type": "activity_type",
             "created_at": "created_at",
@@ -421,7 +407,7 @@
         "crm_activity_fields": {
           "displayName": "Crm Activity Fields",
           "path": "/crm_activity_fields",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "crm_object_type": "crm_object_type",
@@ -440,7 +426,7 @@
         "crm_users": {
           "displayName": "Crm Users",
           "path": "/crm_users",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "crm_id": "crm_id",
@@ -456,7 +442,7 @@
         "custom_fields": {
           "displayName": "Custom Fields",
           "path": "/custom_fields",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "field_type": "field_type",
@@ -470,17 +456,17 @@
         "custom_roles": {
           "displayName": "Custom Roles",
           "path": "/custom_roles",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "id": "id",
             "name": "name"
           },
           "docs": "https://developers.salesloft.com/docs/api/custom-roles-index"
         },
-        "data_control_requests": {
+        "data_control/requests": {
           "displayName": "Requests",
-          "path": "/data_control_requests",
-          "locatedAt": "data",
+          "path": "/data_control/requests",
+          "responseKey": "data",
           "fields": {
             "dry_run": "dry_run",
             "events": "events",
@@ -495,7 +481,7 @@
         "email_template_attachments": {
           "displayName": "Email Template Attachments",
           "path": "/email_template_attachments",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "attachment_content_type": "attachment_content_type",
             "attachment_file_size": "attachment_file_size",
@@ -512,7 +498,7 @@
         "email_templates": {
           "displayName": "Email Templates",
           "path": "/email_templates",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "_links": "_links",
             "archived_at": "archived_at",
@@ -536,10 +522,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/email-templates-index"
         },
-        "external_configurations": {
+        "external/configurations": {
           "displayName": "Team Configurations",
-          "path": "/external_configurations",
-          "locatedAt": "data",
+          "path": "/external/configurations",
+          "responseKey": "data",
           "fields": {
             "deleted": "deleted",
             "external_type": "external_type",
@@ -550,10 +536,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/external-configurations-index"
         },
-        "external_mappings": {
+        "external/mappings": {
           "displayName": "Team Mappings",
-          "path": "/external_mappings",
-          "locatedAt": "data",
+          "path": "/external/mappings",
+          "responseKey": "data",
           "fields": {
             "deleted": "deleted",
             "external_id": "external_id",
@@ -569,7 +555,7 @@
         "groups": {
           "displayName": "Groups",
           "path": "/groups",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "accessible_groups": "accessible_groups",
             "id": "id",
@@ -581,7 +567,7 @@
         "imports": {
           "displayName": "Imports",
           "path": "/imports",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "current_people_count": "current_people_count",
@@ -595,10 +581,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/imports-index"
         },
-        "integrations_signals_registrations": {
+        "integrations/signals/registrations": {
           "displayName": "Signal Registrations",
-          "path": "/integrations_signals_registrations",
-          "locatedAt": "data",
+          "path": "/integrations/signals/registrations",
+          "responseKey": "data",
           "fields": {
             "attribution": "attribution",
             "created_at": "created_at",
@@ -615,10 +601,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/integrations-signals-registrations-index"
         },
-        "integrations_signals_registrations_plays": {
+        "integrations/signals/registrations/plays": {
           "displayName": "Play Registrations",
-          "path": "/integrations_signals_registrations_plays",
-          "locatedAt": "data",
+          "path": "/integrations/signals/registrations/plays",
+          "responseKey": "data",
           "fields": {
             "attributes": "attributes",
             "created_at": "created_at",
@@ -641,7 +627,7 @@
         "meetings": {
           "displayName": "Meetings",
           "path": "/meetings",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "account_id": "account_id",
             "all_day": "all_day",
@@ -652,6 +638,8 @@
             "calendar_id": "calendar_id",
             "calendar_type": "calendar_type",
             "canceled_at": "canceled_at",
+            "count_towards_meetings_attended_metric": "count_towards_meetings_attended_metric",
+            "count_towards_meetings_booked_metric": "count_towards_meetings_booked_metric",
             "created_at": "created_at",
             "crm": "crm",
             "crm_custom_fields": "crm_custom_fields",
@@ -692,7 +680,7 @@
         "notes": {
           "displayName": "Notes",
           "path": "/notes",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "associated_type": "associated_type",
             "associated_with": "associated_with",
@@ -708,7 +696,7 @@
         "opportunities": {
           "displayName": "Opportunities",
           "path": "/opportunities",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "account": "account",
             "account_crm_id": "account_crm_id",
@@ -747,9 +735,9 @@
           "docs": "https://developers.salesloft.com/docs/api/opportunities-index"
         },
         "opportunity_people": {
-          "displayName": "Opportunity/Person Associations",
+          "displayName": "Opportunity And Person Associations",
           "path": "/opportunity_people",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "created_at": "created_at",
             "id": "id",
@@ -764,7 +752,7 @@
         "opportunity_stages": {
           "displayName": "Opportunity Stages",
           "path": "/opportunity_stages",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "active": "active",
             "created_at": "created_at",
@@ -778,7 +766,7 @@
         "pending_emails": {
           "displayName": "Pending Emails",
           "path": "/pending_emails",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "id": "id",
             "mailbox": "mailbox",
@@ -789,7 +777,7 @@
         "people": {
           "displayName": "People",
           "path": "/people",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "account": "account",
             "account_crm_id": "account_crm_id",
@@ -855,7 +843,7 @@
         "person_stages": {
           "displayName": "Person Stages",
           "path": "/person_stages",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "active": "active",
             "created_at": "created_at",
@@ -869,7 +857,7 @@
         "phone_number_assignments": {
           "displayName": "Phone Number Assignments",
           "path": "/phone_number_assignments",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "id": "id",
             "number": "number",
@@ -877,10 +865,10 @@
           },
           "docs": "https://developers.salesloft.com/docs/api/phone-number-assignments-index"
         },
-        "phone_numbers_caller_ids": {
+        "phone_numbers/caller_ids": {
           "displayName": "Caller Ids",
-          "path": "/phone_numbers_caller_ids",
-          "locatedAt": "data",
+          "path": "/phone_numbers/caller_ids",
+          "responseKey": "data",
           "fields": {
             "account_name": "account_name",
             "display_name": "display_name",
@@ -892,7 +880,7 @@
         "saved_list_views": {
           "displayName": "Saved List Views",
           "path": "/saved_list_views",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "id": "id",
             "is_default": "is_default",
@@ -906,7 +894,7 @@
         "steps": {
           "displayName": "Steps",
           "path": "/steps",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "cadence": "cadence",
             "created_at": "created_at",
@@ -926,7 +914,7 @@
         "successes": {
           "displayName": "Successes",
           "path": "/successes",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "counts": "counts",
             "created_at": "created_at",
@@ -947,7 +935,7 @@
         "tags": {
           "displayName": "Team Tags",
           "path": "/tags",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "id": "id",
             "name": "name"
@@ -957,7 +945,7 @@
         "tasks": {
           "displayName": "Tasks",
           "path": "/tasks",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "completed_at": "completed_at",
             "completed_by": "completed_by",
@@ -982,7 +970,7 @@
         "team_template_attachments": {
           "displayName": "Team Template Attachments",
           "path": "/team_template_attachments",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "attachment_file_size": "attachment_file_size",
             "attachment_id": "attachment_id",
@@ -996,7 +984,7 @@
         "team_templates": {
           "displayName": "Team Templates",
           "path": "/team_templates",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "_links": "_links",
             "archived_at": "archived_at",
@@ -1020,7 +1008,7 @@
         "users": {
           "displayName": "Users",
           "path": "/users",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "email": "email",
             "guid": "guid",
@@ -1032,7 +1020,7 @@
         "webhook_subscriptions": {
           "displayName": "Webhook Subscriptions",
           "path": "/webhook_subscriptions",
-          "locatedAt": "data",
+          "responseKey": "data",
           "fields": {
             "callback_token": "callback_token",
             "callback_url": "callback_url",
diff --git a/scripts/scraper/salesloft/metadata/main.go b/scripts/scraper/salesloft/metadata/main.go
index 86356a49..9b10aeff 100644
--- a/scripts/scraper/salesloft/metadata/main.go
+++ b/scripts/scraper/salesloft/metadata/main.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"flag"
-	"fmt"
 	"log"
 	"log/slog"
 	"strings"
@@ -12,6 +11,7 @@ import (
 	"github.com/amp-labs/connectors/internal/datautils"
 	"github.com/amp-labs/connectors/internal/goutils"
 	"github.com/amp-labs/connectors/internal/staticschema"
+	"github.com/amp-labs/connectors/providers/salesloft"
 	"github.com/amp-labs/connectors/providers/salesloft/metadata"
 	"github.com/amp-labs/connectors/tools/scrapper"
 	"github.com/iancoleman/strcase"
@@ -22,6 +22,13 @@ const (
 	SalesloftDocsPrefixURL = "https://developers.salesloft.com"
 	// ModelIndexURL - on this page all links to dedicated Models can be found.
 	ModelIndexURL = "https://developers.salesloft.com/docs/api"
+
+	ConnectorBaseURL = "https://api.salesloft.com"
+)
+
+var excludedDocumentation = datautils.NewSet( // nolint:gochecknoglobals
+	"/docs/api/bulk-jobs-job-data-index/",
+	"/docs/api/bulk-jobs-results-index/",
 )
 
 var withQueryParamStats bool // nolint:gochecknoglobals
@@ -45,24 +52,36 @@ func main() {
 	slog.Info("Completed.")
 }
 
+func formatObjectURL(fullPath string) string {
+	urlPath, _ := strings.CutPrefix(fullPath, ConnectorBaseURL+"/"+salesloft.ApiVersion)
+
+	return urlPath
+}
+
 func createIndex() {
 	sections := getSectionLinks()
 
 	registry := scrapper.NewModelURLRegistry()
 
-	for i, section := range sections {
-		doc := queryHTML(SalesloftDocsPrefixURL + "/" + section)
+	for index, section := range sections {
+		doc := queryHTML(SalesloftDocsPrefixURL + section)
 
 		doc.Find(".theme-doc-markdown article").Each(func(i int, s *goquery.Selection) {
 			cell := s.Find("a")
 			path, _ := cell.Attr("href")
 			name, _ := cell.Find("h2").Attr("title")
+
+			if excludedDocumentation.Has(path) {
+				return
+			}
+
 			registry.Add(name, SalesloftDocsPrefixURL+path)
 		})
-		log.Printf("Index completed %.2f%%\n", getPercentage(i, len(sections))) // nolint:forbidigo
-	}
+		log.Printf("Index completed %.2f%%\n", getPercentage(index, len(sections))) // nolint:forbidigo
 
-	goutils.MustBeNil(metadata.FileManager.SaveIndex(registry))
+		// Update file after each iteration.
+		goutils.MustBeNil(metadata.FileManager.SaveIndex(registry))
+	}
 }
 
 func createSchemas() {
@@ -76,30 +95,37 @@ func createSchemas() {
 		model := filteredListDocs[i]
 		doc := 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) {
-					// Sometimes there are nested fields we ignore them
-					// Only the first most field represents top level fields of response payload
-					fieldName := property.Find(`strong`).First().Text()
-					if len(fieldName) != 0 {
-						newDisplayName, isList := handleDisplayName(model.DisplayName)
-						if isList {
-							schemas.Add("", modelName, newDisplayName, fmt.Sprintf("/%v", modelName), "data",
-								staticschema.FieldMetadataMapV1{
-									fieldName: fieldName,
-								}, &model.URL, nil)
-						}
+		endpointPath := doc.Find(".openapi__method-endpoint-path").Text()
+		urlPath := formatObjectURL(endpointPath)
+		objectName, _ := strings.CutPrefix(urlPath, "/")
+
+		doc.Find(`.openapi-tabs__schema-container .openapi-schema__property`).
+			Each(func(i int, property *goquery.Selection) {
+				// Sometimes there are nested fields we ignore them
+				// Only the first most field represents top level fields of response payload
+				if !scrapper.Query.IsVisible(property) {
+					return
+				}
+
+				fieldName := property.Text()
+				if len(fieldName) != 0 {
+					newDisplayName, isList := handleDisplayName(model.DisplayName)
+					if isList {
+						schemas.Add("", objectName, newDisplayName, urlPath, "data",
+							staticschema.FieldMetadataMapV1{
+								fieldName: fieldName,
+							}, &model.URL, nil)
 					}
-				})
+				}
 			})
 
-		log.Printf("Schemas completed %.2f%% [%v]\n", getPercentage(i, len(filteredListDocs)), modelName)
+		log.Printf("Schemas completed %.2f%% [%v]\n", getPercentage(i, len(filteredListDocs)), objectName)
+
+		// Update file after each iteration.
+		goutils.MustBeNil(metadata.FileManager.FlushSchemas(schemas))
 	}
 
+	// Finalized save.
 	goutils.MustBeNil(metadata.FileManager.SaveSchemas(schemas))
 }
 
@@ -201,10 +227,7 @@ func handleDisplayName(name string) (displayName string, isListResource bool) {
 }
 
 func queryHTML(sourceURL string) *goquery.Document {
-	// must end with `/` to avoid stupid redirect with then without then again with and without slash
-	if !strings.HasSuffix(sourceURL, "/") {
-		sourceURL += "/"
-	}
+	const waitInterval = 2 // seconds
 
-	return scrapper.QueryHTML(sourceURL)
+	return scrapper.QueryLoadableHTML(sourceURL, waitInterval)
 }
diff --git a/test/salesloft/read/main.go b/test/salesloft/read/people/main.go
similarity index 100%
rename from test/salesloft/read/main.go
rename to test/salesloft/read/people/main.go
diff --git a/tools/scrapper/query.go b/tools/scrapper/query.go
new file mode 100644
index 00000000..0409f442
--- /dev/null
+++ b/tools/scrapper/query.go
@@ -0,0 +1,32 @@
+package scrapper
+
+import (
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+)
+
+type query struct{}
+
+var Query = query{} // nolint:gochecknoglobals
+
+func (query) IsVisible(selection *goquery.Selection) bool {
+	// Check if the element itself has display: none or visibility: hidden
+	style, exists := selection.Attr("style")
+	if exists && (strings.Contains(style, "display: none") || strings.Contains(style, "visibility: hidden")) {
+		return false
+	}
+
+	// Traverse up the parent elements to check if any ancestor is hidden
+	parent := selection.Parent()
+	for parent.Length() > 0 {
+		style, exists = parent.Attr("style")
+		if exists && (strings.Contains(style, "display: none") || strings.Contains(style, "visibility: hidden")) {
+			return false
+		}
+
+		parent = parent.Parent()
+	}
+
+	return true
+}
diff --git a/tools/scrapper/utils.go b/tools/scrapper/utils.go
index 1b8bb4ad..bfaab859 100644
--- a/tools/scrapper/utils.go
+++ b/tools/scrapper/utils.go
@@ -3,66 +3,52 @@ package scrapper
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"log"
-	"net/http"
 	"os"
+	"strings"
+	"time"
 
 	"github.com/PuerkitoBio/goquery"
+	"github.com/chromedp/chromedp"
 )
 
 // QueryHTML this method can panic.
 func QueryHTML(url string) *goquery.Document { //nolint:gocritic
-	res, err := makeRequest(url)
+	return QueryLoadableHTML(url, 0)
+}
+
+// QueryLoadableHTML this method can panic.
+// Uses browser internally to wait for all HTML parts to be fully loaded.
+func QueryLoadableHTML(url string, wait int64) *goquery.Document { //nolint:gocritic
+	htmlPage, err := loadHTMLPage(url, wait)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	if res.StatusCode != http.StatusOK {
-		_ = res.Body.Close()
-		log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
-	}
+	reader := strings.NewReader(htmlPage)
 
-	doc, err := goquery.NewDocumentFromReader(res.Body)
+	doc, err := goquery.NewDocumentFromReader(reader)
 	if err != nil {
-		_ = res.Body.Close()
-
 		log.Fatal(err)
 	}
 
-	_ = res.Body.Close()
-
 	return doc
 }
 
-func makeRequest(sourceURL string) (*http.Response, error) {
-	client := &http.Client{
-		CheckRedirect: func() func(req *http.Request, via []*http.Request) error {
-			redirects := 0
-
-			return func(req *http.Request, via []*http.Request) error {
-				maxRedirects := 12
-				if redirects > maxRedirects {
-					return fmt.Errorf("stopped after %v redirects", maxRedirects) // nolint:goerr113
-				}
-				redirects++
+func loadHTMLPage(url string, wait int64) (string, error) {
+	ctx, cancel := chromedp.NewContext(context.Background())
+	defer cancel()
 
-				return nil
-			}
-		}(),
-	}
-
-	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, sourceURL, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	res, err := client.Do(req)
-	if err != nil {
-		return nil, err
-	}
+	// Run Chrome headless and extract the final rendered HTML.
+	var htmlContent string
+	err := chromedp.Run(ctx,
+		chromedp.Navigate(url),
+		// Wait for JavaScript to load content
+		chromedp.Sleep(time.Duration(wait)*time.Second),
+		chromedp.OuterHTML("html", &htmlContent),
+	)
 
-	return res, nil
+	return htmlContent, err
 }
 
 func LoadFile(filename string, object any) error {


Test patch:
diff --git a/providers/salesloft/delete_test.go b/providers/salesloft/delete_test.go
index 820aeefe..a18d7fdb 100644
--- a/providers/salesloft/delete_test.go
+++ b/providers/salesloft/delete_test.go
@@ -33,9 +33,10 @@ func TestDelete(t *testing.T) { // nolint:funlen,cyclop
 		{
 			Name:  "Correct error message is understood from JSON response",
 			Input: common.DeleteParams{ObjectName: "signals", RecordId: "22165"},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusUnprocessableEntity, listSchema),
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If:    mockcond.PathSuffix("/v2/signals/22165"),
+				Then:  mockserver.Response(http.StatusUnprocessableEntity, listSchema),
 			}.Server(),
 			ExpectedErrs: []error{
 				common.ErrBadRequest,
@@ -47,8 +48,11 @@ func TestDelete(t *testing.T) { // nolint:funlen,cyclop
 			Input: common.DeleteParams{ObjectName: "signals", RecordId: "22165"},
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodDELETE(),
-				Then:  mockserver.Response(http.StatusNoContent),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/signals/22165"),
+					mockcond.MethodDELETE(),
+				},
+				Then: mockserver.Response(http.StatusNoContent),
 			}.Server(),
 			Expected:     &common.DeleteResult{Success: true},
 			ExpectedErrs: nil,
diff --git a/providers/salesloft/metadata_test.go b/providers/salesloft/metadata_test.go
index fe5af1d4..bb56fa37 100644
--- a/providers/salesloft/metadata_test.go
+++ b/providers/salesloft/metadata_test.go
@@ -26,19 +26,22 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			ExpectedErrs: []error{common.ErrObjectNotSupported},
 		},
 		{
-			Name:   "Successfully describe one object with metadata",
-			Input:  []string{"bulk_jobs_results"},
-			Server: mockserver.Dummy(),
+			Name:       "Successfully describe one object with metadata",
+			Input:      []string{"activities/calls"},
+			Server:     mockserver.Dummy(),
+			Comparator: testroutines.ComparatorSubsetMetadata,
 			Expected: &common.ListObjectMetadataResult{
 				Result: map[string]common.ObjectMetadata{
-					"bulk_jobs_results": {
-						DisplayName: "Job Data for a Completed Bulk Job.",
+					"activities/calls": {
+						DisplayName: "Calls",
 						FieldsMap: map[string]string{
-							"error":    "error",
-							"id":       "id",
-							"record":   "record",
-							"resource": "resource",
-							"status":   "status",
+							"disposition": "disposition",
+							"duration":    "duration",
+							"id":          "id",
+							"note":        "note",
+							"positive":    "positive",
+							"recordings":  "recordings",
+							"sentiment":   "sentiment",
 						},
 					},
 				},
diff --git a/providers/salesloft/read_test.go b/providers/salesloft/read_test.go
index 60aa544b..89c12be4 100644
--- a/providers/salesloft/read_test.go
+++ b/providers/salesloft/read_test.go
@@ -93,9 +93,10 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		{
 			Name:  "Next page URL is correctly inferred",
 			Input: common.ReadParams{ObjectName: "people", Fields: connectors.Fields("id")},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusOK, responseListPeople),
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If:    mockcond.PathSuffix("/v2/people"),
+				Then:  mockserver.Response(http.StatusOK, responseListPeople),
 			}.Server(),
 			Comparator: testroutines.ComparatorPagination,
 			Expected: &common.ReadResult{
@@ -108,9 +109,10 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 		{
 			Name:  "Successful read with 25 entries, checking one row",
 			Input: common.ReadParams{ObjectName: "people", Fields: connectors.Fields("id")},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusOK, responseListPeople),
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If:    mockcond.PathSuffix("/v2/people"),
+				Then:  mockserver.Response(http.StatusOK, responseListPeople),
 			}.Server(),
 			Comparator: testroutines.ComparatorSubsetRead,
 			Expected: &common.ReadResult{
@@ -138,9 +140,10 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 				ObjectName: "people",
 				Fields:     connectors.Fields("email_address", "person_company_website"),
 			},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusOK, responseListPeople),
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If:    mockcond.PathSuffix("/v2/people"),
+				Then:  mockserver.Response(http.StatusOK, responseListPeople),
 			}.Server(),
 			Comparator: testroutines.ComparatorSubsetRead,
 			Expected: &common.ReadResult{
@@ -168,9 +171,10 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 				ObjectName: "users",
 				Fields:     connectors.Fields("email", "guid"),
 			},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusOK, responseListUsers),
+			Server: mockserver.Conditional{
+				Setup: mockserver.ContentJSON(),
+				If:    mockcond.PathSuffix("/v2/users"),
+				Then:  mockserver.Response(http.StatusOK, responseListUsers),
 			}.Server(),
 			Comparator: testroutines.ComparatorSubsetRead,
 			Expected: &common.ReadResult{
@@ -196,8 +200,11 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			Input: common.ReadParams{ObjectName: "accounts", Fields: connectors.Fields("id")},
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
-				If:    mockcond.QueryParamsMissing("updated_at[gte]"),
-				Then:  mockserver.Response(http.StatusOK, responseListAccounts),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/accounts"),
+					mockcond.QueryParamsMissing("updated_at[gte]"),
+				},
+				Then: mockserver.Response(http.StatusOK, responseListAccounts),
 			}.Server(),
 			Comparator:   testroutines.ComparatorPagination,
 			Expected:     &common.ReadResult{Rows: 4, NextPage: "", Done: true},
@@ -212,8 +219,11 @@ func TestRead(t *testing.T) { //nolint:funlen,gocognit,cyclop,maintidx
 			},
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
-				If:    mockcond.QueryParam("updated_at[gte]", "2024-06-07T10:51:20.851224-04:00"),
-				Then:  mockserver.Response(http.StatusOK, responseListAccountsSince),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/accounts"),
+					mockcond.QueryParam("updated_at[gte]", "2024-06-07T10:51:20.851224-04:00"),
+				},
+				Then: mockserver.Response(http.StatusOK, responseListAccountsSince),
 			}.Server(),
 			Comparator:   testroutines.ComparatorPagination,
 			Expected:     &common.ReadResult{Rows: 2, NextPage: "", Done: true},
diff --git a/providers/salesloft/write_test.go b/providers/salesloft/write_test.go
index 3b277031..8098b1ec 100644
--- a/providers/salesloft/write_test.go
+++ b/providers/salesloft/write_test.go
@@ -49,8 +49,11 @@ func TestWrite(t *testing.T) { // nolint:funlen,cyclop
 			Input: common.WriteParams{ObjectName: "signals", RecordData: "dummy"},
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPOST(),
-				Then:  mockserver.Response(http.StatusOK),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/signals"),
+					mockcond.MethodPOST(),
+				},
+				Then: mockserver.Response(http.StatusOK),
 			}.Server(),
 			Expected:     &common.WriteResult{Success: true},
 			ExpectedErrs: nil,
@@ -61,6 +64,7 @@ func TestWrite(t *testing.T) { // nolint:funlen,cyclop
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
 				If: mockcond.And{
+					mockcond.PathSuffix("/v2/signals/22165"),
 					mockcond.MethodPUT(),
 				},
 				Then: mockserver.Response(http.StatusOK),
@@ -73,8 +77,11 @@ func TestWrite(t *testing.T) { // nolint:funlen,cyclop
 			Input: common.WriteParams{ObjectName: "accounts", RecordData: "dummy"},
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPOST(),
-				Then:  mockserver.Response(http.StatusOK, createAccountRes),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/accounts"),
+					mockcond.MethodPOST(),
+				},
+				Then: mockserver.Response(http.StatusOK, createAccountRes),
 			}.Server(),
 			Comparator: testroutines.ComparatorSubsetWrite,
 			Expected: &common.WriteResult{
@@ -96,8 +103,11 @@ func TestWrite(t *testing.T) { // nolint:funlen,cyclop
 			Input: common.WriteParams{ObjectName: "tasks", RecordData: "dummy"},
 			Server: mockserver.Conditional{
 				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPOST(),
-				Then:  mockserver.Response(http.StatusOK, createTaskRes),
+				If: mockcond.And{
+					mockcond.PathSuffix("/v2/tasks"),
+					mockcond.MethodPOST(),
+				},
+				Then: mockserver.Response(http.StatusOK, createTaskRes),
 			}.Server(),
 			Comparator: testroutines.ComparatorSubsetWrite,
 			Expected: &common.WriteResult{
diff --git a/test/salesloft/read/calls/main.go b/test/salesloft/read/calls/main.go
new file mode 100644
index 00000000..15e3ca47
--- /dev/null
+++ b/test/salesloft/read/calls/main.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"github.com/amp-labs/connectors"
+	"github.com/amp-labs/connectors/common"
+	msTest "github.com/amp-labs/connectors/test/salesloft"
+	"github.com/amp-labs/connectors/test/utils"
+)
+
+func main() {
+	// Handle Ctrl-C gracefully.
+	ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
+	defer done()
+
+	// Set up slog logging.
+	utils.SetupLogging()
+
+	conn := msTest.GetSalesloftConnector(ctx)
+
+	res, err := conn.Read(ctx, common.ReadParams{
+		ObjectName: "activities/calls",
+		Since:      time.Now().Add(-1 * time.Second * 60 * 4),
+		Fields:     connectors.Fields("notes", "created_at", "recordings"),
+	})
+	if err != nil {
+		utils.Fail("error reading from Salesloft", "error", err)
+	}
+
+	fmt.Println("Reading people..")
+	utils.DumpJSON(res, os.Stdout)
+}