Repository: amp-labs/connectors
Pull request #1482 (2025-03-14 14:26:43)

[CON-1610] feat(attio): added support to standard & custom object
### Notes
[https://github.com/amp-labs/connectors/pull/1046](url)
**Added** - standard and custom objects 
**Removed** - objects and webhooks module

### Functionalities
Handle both Attio APIs and standard/custom objects.
![image](https://github.com/user-attachments/assets/314ca15c-1e05-4b44-a9d0-2e5609c0b5ea)

Fetch the metadata from the api_slug for standard/custom objects.
![image](https://github.com/user-attachments/assets/c98833d6-697e-4af1-9bfa-c502c39de2cc)

### Metadata result
![image](https://github.com/user-attachments/assets/75856ecf-fcaf-4884-b488-9b4056c6b691)     


Patch:
diff --git a/providers/attio/README.md b/providers/attio/README.md
index 3ad484eb..d4ef9399 100644
--- a/providers/attio/README.md
+++ b/providers/attio/README.md
@@ -1,4 +1,4 @@
-# Atio connector
+# Attio connector
 
 
 ## Supported Objects 
@@ -8,9 +8,30 @@ Attio API version : v2
 
 | Object | Resource | Method
 | :-------- | :------- | 
-| Objects  | objects | read and write
 | Lists | lists | read and write
 | Workspace members | workspace_members | read
-| Webhooks | webhooks | read and write
 | Tasks  | tasks | read and write
-| Notes  | notes | read and write
\ No newline at end of file
+| Notes  | notes | read and write
+| People | people| read
+| Companies | companies | read
+| Users | users | read
+| Deals | deals | read
+| Workspaces | workspaces | read
+
+The objects mentioned above, such as People, Companies, Users, Deals, and Workspaces, are Attio standard objects. The remaining objects, like Lists, Workspace members, Tasks, and Notes, are Attio non-standard objects.
+
+To differentiate between the two types in the script, introduce a variable for non-standard objects like 'supportAttioApi' and for standard/custom objects, use the variable 'isAttioStandardOrCustomObj.' Here, 'standard' isn't a term we commonly use, but rather a concept defined by Attio itself.
+
+Attio API Reference: https://developers.attio.com/reference/
+
+## Getting Metadata
+For standard/custom objects, 
+Used endpoint to get the display name: https://developers.attio.com/reference/get_v2-objects-object
+Used endpoint to get fields: https://developers.attio.com/reference/get_v2-target-identifier-attributes
+
+For non-standard objects, directly used the object's endpoint.
+
+## Read Functions
+For standard/custom objects, a separate read function "readStandardOrCustomObject" is used, which calls the endpoint to get the response: https://developers.attio.com/reference/post_v2-objects-object-records-query
+
+For non-standard object, a separate read function "readGeneralAPI" is used and directly used the object's endpoint to get the response.
diff --git a/providers/attio/metadata.go b/providers/attio/metadata.go
index 730e0e69..43fda3aa 100644
--- a/providers/attio/metadata.go
+++ b/providers/attio/metadata.go
@@ -2,8 +2,10 @@ package attio
 
 import (
 	"context"
+	"time"
 
 	"github.com/amp-labs/connectors/common"
+	"github.com/amp-labs/connectors/common/urlbuilder"
 )
 
 // This struct is used for when the response data having slice of data.
@@ -11,7 +13,42 @@ type responseObject struct {
 	Data []map[string]any `json:"data"`
 }
 
+// This struct is used for when the response data having single data.
+type objectAttribute struct {
+	Data []Data `json:"data"`
+}
+
+type Data struct {
+	ID struct {
+		WorkspaceID string `json:"workspace_id"` //nolint:tagliatelle
+		ObjectID    string `json:"object_id"`    //nolint:tagliatelle
+		AttributeID string `json:"attribute_id"` //nolint:tagliatelle
+	} `json:"id"`
+	Title                 string    `json:"title"`
+	APISlug               string    `json:"api_slug"` //nolint:tagliatelle
+	Type                  string    `json:"type"`
+	IsWritable            bool      `json:"is_writable"`              //nolint:tagliatelle
+	IsMultiselect         bool      `json:"is_multiselect"`           //nolint:tagliatelle
+	IsDefaultValueEnabled bool      `json:"is_default_value_enabled"` //nolint:tagliatelle
+	CreatedAt             time.Time `json:"created_at"`               //nolint:tagliatelle
+}
+
+type objectResponse struct {
+	Data struct {
+		Id struct {
+			WorkspaceId string `json:"workspace_id"` //nolint:tagliatelle
+			ObjectId    string `json:"object_id"`    //nolint:tagliatelle
+		} `json:"id"`
+		ApiSlug      string    `json:"api_slug"`      //nolint:tagliatelle
+		SingularNoun string    `json:"singular_noun"` //nolint:tagliatelle
+		PluralNoun   string    `json:"plural_noun"`   //nolint:tagliatelle
+		CreatedAt    time.Time `json:"created_at"`    //nolint:tagliatelle
+	} `json:"data"`
+}
+
 // ListObjectMetadata creates metadata of object via reading objects using Attio API.
+//
+//nolint:funlen
 func (c *Connector) ListObjectMetadata(ctx context.Context,
 	objectNames []string,
 ) (*common.ListObjectMetadataResult, error) {
@@ -19,44 +56,110 @@ func (c *Connector) ListObjectMetadata(ctx context.Context,
 		return nil, common.ErrMissingObjects
 	}
 
-	metadataResult := common.ListObjectMetadataResult{
-		Result: make(map[string]common.ObjectMetadata),
-		Errors: make(map[string]error),
-	}
+	metadataResult := common.NewListObjectMetadataResult()
 
 	for _, obj := range objectNames {
-		// Constructing the request url.
-		url, err := c.getApiURL(obj)
-		if err != nil {
-			return nil, err
-		}
-
-		resp, err := c.Client.Get(ctx, url.String())
+		metadata, isCustom, err := c.getObjectAttributes(ctx, obj)
 		if err != nil {
 			metadataResult.Errors[obj] = err
 
 			continue
 		}
 
-		var metadata *common.ObjectMetadata
+		displayName := obj
 
-		metadata, err = parseMetadataFromResponse(resp)
-		if err != nil {
-			metadataResult.Errors[obj] = err
+		if isCustom {
+			name, err := c.getObjectDisplayName(ctx, obj)
+			if err != nil {
+				metadataResult.Errors[obj] = err
 
-			continue
+				continue
+			}
+
+			if name != "" {
+				displayName = name
+			}
 		}
 
-		metadata.DisplayName = obj
-		metadataResult.Result[obj] = *metadata
+		metadataResult.Result[obj] = *common.NewObjectMetadata(
+			displayName, metadata,
+		)
 	}
 
-	return &metadataResult, nil
+	return metadataResult, nil
 }
 
-// This function is used for slice of objects.
-func parseMetadataFromResponse(resp *common.JSONHTTPResponse) (*common.ObjectMetadata, error) {
-	response, err := common.UnmarshalJSON[responseObject](resp)
+func (c *Connector) getObjectAttributes(
+	ctx context.Context, obj string,
+) (map[string]common.FieldMetadata, bool, error) {
+	// Standard isn't a term we commonly use, but rather a concept defined by Attio itself.
+	// supportAttioApi represents the APIs listed under the Attio API section in the docs
+	// (this does not cover the entire Attio API). Reference: https://developers.attio.com/reference.
+	isAttioStandardOrCustomObj := !supportAttioApi.Has(obj)
+
+	var (
+		url *urlbuilder.URL
+		err error
+	)
+
+	if isAttioStandardOrCustomObj {
+		url, err = c.getObjectAttributesURL(obj)
+		if err != nil {
+			return nil, false, err
+		}
+	} else {
+		url, err = c.getApiURL(obj)
+		if err != nil {
+			return nil, false, err
+		}
+	}
+
+	resp, err := c.Client.Get(ctx, url.String())
+	if err != nil {
+		return nil, false, err
+	}
+
+	metadata, err := c.parseMetadataFromResponse(ctx, resp, isAttioStandardOrCustomObj)
+	if err != nil {
+		return nil, false, err
+	}
+
+	return metadata, isAttioStandardOrCustomObj, nil
+}
+
+// getObjectDisplayName fetches the display name for custom objects.
+func (c *Connector) getObjectDisplayName(ctx context.Context, obj string) (string, error) {
+	url, err := c.getObjectsURL(obj)
+	if err != nil {
+		return "", err
+	}
+
+	resp, err := c.Client.Get(ctx, url.String())
+	if err != nil {
+		return "", err
+	}
+
+	return getDisplayName(resp)
+}
+
+func (c *Connector) parseMetadataFromResponse(ctx context.Context, resp *common.JSONHTTPResponse,
+	isAttioStandardOrCustomObj bool,
+) (map[string]common.FieldMetadata, error) {
+	// Retrieving metadata for standard and custom objects in Attio using the api_slug field.
+	if isAttioStandardOrCustomObj {
+		return c.parseStandardOrCustomMetadata(ctx, resp)
+	}
+
+	return c.parseMetadata(resp)
+}
+
+// Parsing the metadata response for standard or custom objects.
+func (c *Connector) parseStandardOrCustomMetadata(
+	ctx context.Context, resp *common.JSONHTTPResponse,
+) (map[string]common.FieldMetadata, error) {
+	metadata := make(map[string]common.FieldMetadata)
+
+	response, err := common.UnmarshalJSON[objectAttribute](resp)
 	if err != nil {
 		return nil, err
 	}
@@ -65,14 +168,123 @@ func parseMetadataFromResponse(resp *common.JSONHTTPResponse) (*common.ObjectMet
 		return nil, common.ErrMissingExpectedValues
 	}
 
-	metadata := &common.ObjectMetadata{
-		FieldsMap: make(map[string]string),
+	for _, value := range response.Data {
+		apiSlug := value.APISlug
+
+		var defaultValues []common.FieldValue
+
+		if value.Type == "select" || value.Type == "record-reference" || value.Type == "domain" {
+			defaultValues, err = c.getDefaultValues(ctx, value)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		metadata[apiSlug] = common.FieldMetadata{
+			DisplayName:  apiSlug,
+			ValueType:    getFieldValueType(value.Type, value.IsMultiselect),
+			ProviderType: value.Type,
+			ReadOnly:     !value.IsWritable,
+			Values:       defaultValues,
+		}
+	}
+
+	return metadata, nil
+}
+
+// Parsing the metadata response for non-standard or custom objects.
+func (c *Connector) parseMetadata(resp *common.JSONHTTPResponse) (map[string]common.FieldMetadata, error) {
+	metadata := make(map[string]common.FieldMetadata)
+
+	response, err := common.UnmarshalJSON[responseObject](resp)
+	if err != nil {
+		return nil, err
 	}
 
+	if len(response.Data) == 0 {
+		return nil, common.ErrMissingExpectedValues
+	}
 	// Using the first result data to generate the metadata.
 	for k := range response.Data[0] {
-		metadata.FieldsMap[k] = k
+		metadata[k] = common.FieldMetadata{
+			DisplayName:  k,
+			ValueType:    common.ValueTypeOther,
+			ProviderType: "", // not available
+			ReadOnly:     false,
+			Values:       nil,
+		}
 	}
 
 	return metadata, nil
 }
+
+// Getting display name for both standard and custom objects.
+func getDisplayName(resp *common.JSONHTTPResponse) (string, error) {
+	response, err := common.UnmarshalJSON[objectResponse](resp)
+	if err != nil {
+		return "", err
+	}
+
+	if response == nil {
+		return "", common.ErrMissingExpectedValues
+	}
+
+	res := response.Data.PluralNoun
+	if res == "" {
+		return "", common.ErrNotFound
+	}
+
+	return res, nil
+}
+
+func getFieldValueType(field string, ismultiselect bool) common.ValueType {
+	switch field {
+	case "number":
+		return common.ValueTypeInt
+	case "text":
+		return common.ValueTypeString
+	case "select", "record-reference", "domain":
+		if ismultiselect {
+			return common.ValueTypeMultiSelect
+		}
+
+		return common.ValueTypeSingleSelect
+	case "date":
+		return common.ValueTypeDate
+	case "timestamp":
+		return common.ValueTypeDateTime
+	default:
+		// location, currency, interaction, actor-reference
+		return common.ValueTypeOther
+	}
+}
+
+func (c *Connector) getDefaultValues(ctx context.Context, o Data) (fields []common.FieldValue, err error) {
+	if !o.IsDefaultValueEnabled {
+		return nil, nil
+	}
+
+	url, err := c.getOptionsURL(o.ID.ObjectID, o.ID.AttributeID)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.Client.Get(ctx, url.String())
+	if err != nil {
+		return nil, err
+	}
+
+	objectAttribute, err := common.UnmarshalJSON[objectAttribute](resp)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, title := range objectAttribute.Data {
+		fields = append(fields, common.FieldValue{
+			Value:        title.Title,
+			DisplayValue: title.Title,
+		})
+	}
+
+	return
+}
diff --git a/providers/attio/objectNames.go b/providers/attio/objectNames.go
index 176209ff..6446ed0f 100644
--- a/providers/attio/objectNames.go
+++ b/providers/attio/objectNames.go
@@ -3,33 +3,28 @@ package attio
 import "github.com/amp-labs/connectors/internal/datautils"
 
 const (
-	objectNameObjects          = "objects"
 	objectNameLists            = "lists"
 	objectNameWorkspacemembers = "workspace_members"
-	objectNameWebhooks         = "webhooks"
 	objectNameTasks            = "tasks"
 	objectNameNotes            = "notes"
 )
 
-var supportedObjectsByRead = datautils.NewSet( //nolint:gochecknoglobals
-	objectNameObjects,
+var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameLists,
-	objectNameWorkspacemembers,
-	objectNameWebhooks,
 	objectNameTasks,
 	objectNameNotes,
 )
 
-var supportedObjectsByWrite = datautils.NewSet( //nolint:gochecknoglobals
-	objectNameObjects,
-	objectNameLists,
+var supportLimitAndOffset = datautils.NewSet( //nolint:gochecknoglobals
 	objectNameTasks,
 	objectNameNotes,
-	objectNameWebhooks,
 )
 
-var supportLimitAndOffset = datautils.NewSet( //nolint:gochecknoglobals
+// supportAttioApi represents the APIs listed under the Attio API section in the docs
+// (this does not cover the entire Attio API). Reference: https://developers.attio.com/reference.
+var supportAttioApi = datautils.NewSet( //nolint:gochecknoglobals
+	objectNameLists,
+	objectNameWorkspacemembers,
 	objectNameTasks,
 	objectNameNotes,
-	objectNameWebhooks,
 )
diff --git a/providers/attio/params.go b/providers/attio/params.go
index edfccc54..4f932e85 100644
--- a/providers/attio/params.go
+++ b/providers/attio/params.go
@@ -18,7 +18,10 @@ type parameters struct {
 
 const (
 	// DefaultPageSize is number of elements per page.
-	DefaultPageSize = 10
+	// The Page size for standard/custom objects, tasks.
+	DefaultPageSize = 500
+	// The Page size for only notes object.
+	DefaultPageSizeForNotesObj = 50
 )
 
 func (p parameters) ValidateParams() error {
diff --git a/providers/attio/parse.go b/providers/attio/parse.go
index 27d44eb6..2d62726f 100644
--- a/providers/attio/parse.go
+++ b/providers/attio/parse.go
@@ -9,16 +9,16 @@ import (
 	"github.com/spyzhov/ajson"
 )
 
-func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
+func makeNextRecordsURL(reqLink *urlbuilder.URL, obj string) common.NextPageFunc {
 	return func(node *ajson.Node) (string, error) {
-		previousStart := 0
-
 		// Extract the data key value from the response.
 		value, err := jsonquery.New(node).ArrayRequired("data")
 		if err != nil {
 			return "", err
 		}
 
+		previousStart := 0
+
 		if (reqLink.HasQueryParam("limit") || reqLink.HasQueryParam("offset")) && len(value) != 0 {
 			offsetQP, ok := reqLink.GetFirstQueryParam("offset")
 			if ok {
@@ -29,9 +29,9 @@ func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 				}
 			}
 
-			nextStart := previousStart + DefaultPageSize
+			nextStart, pageSize := handlePagination(previousStart, obj)
 
-			reqLink.WithQueryParam("limit", strconv.Itoa(DefaultPageSize))
+			reqLink.WithQueryParam("limit", strconv.Itoa(pageSize))
 			reqLink.WithQueryParam("offset", strconv.Itoa(nextStart))
 
 			return reqLink.String(), nil
@@ -40,3 +40,167 @@ func makeNextRecordsURL(reqLink *urlbuilder.URL) common.NextPageFunc {
 		return "", nil
 	}
 }
+
+// To determine the next page records for the standard/custom objects.
+func makeNextRecordStandardObj(offset int) common.NextPageFunc {
+	return func(node *ajson.Node) (string, error) {
+		// Extract the data key value from the response.
+		value, err := jsonquery.New(node).ArrayRequired("data")
+		if err != nil {
+			return "", err
+		}
+
+		if len(value) == 0 {
+			return "", nil
+		}
+
+		nextStart := offset + DefaultPageSize
+
+		return strconv.Itoa(nextStart), nil
+	}
+}
+
+func handlePagination(previousStart int, obj string) (int, int) {
+	var nextStart, pageSize int
+
+	if obj == objectNameNotes {
+		nextStart = previousStart + DefaultPageSizeForNotesObj
+		pageSize = DefaultPageSizeForNotesObj
+	} else {
+		nextStart = previousStart + DefaultPageSize
+		pageSize = DefaultPageSize
+	}
+
+	return nextStart, pageSize
+}
+
+// standard/custom object has a special field named "values" which holds all the important fields.
+// Therefore, nested "values" will be removed and fields inside the "values" field will be moved
+// to the top level of the object.
+//
+// Example companies(shortened response):
+//
+//		"data": [
+//		{
+//			"id": {
+//			  "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+//			  "object_id": "ffbca575-69c4-4080-bf98-91d79aeea4b1",
+//			  "record_id": "d1b0593a-fb43-4d41-82ab-57fc3db73b3a"
+//			},
+//			"created_at": "2025-03-25T06:44:30.177000000Z",
+//			"values": {
+//			  "record_id": [
+//				{
+//				  "active_from": "2025-03-25T06:44:30.177000000Z",
+//				  "active_until": null,
+//				  "created_by_actor": {
+//					"type": "workspace-member",
+//					"id": "073f4c74-b60d-4de9-992a-0f799b5e442e"
+//				  },
+//				  "value": "d1b0593a-fb43-4d41-82ab-57fc3db73b3a",
+//				  "attribute_type": "text"
+//				}
+//			  ],
+//	       .... (more response data will be there)
+//
+// The resulting fields for the above will be: id, created_at, record_id.
+
+type MarshalledData func([]map[string]any, []string) ([]common.ReadResultRow, error)
+
+func DataMarshall(resp *common.JSONHTTPResponse) MarshalledData {
+	return func(records []map[string]any, fields []string) ([]common.ReadResultRow, error) {
+		node, ok := resp.Body()
+		if !ok {
+			return nil, common.ErrEmptyJSONHTTPResponse
+		}
+
+		arr, err := jsonquery.New(node).ArrayOptional("data")
+		if err != nil {
+			return nil, err
+		}
+
+		flattenrecords, err := flattenRecords(arr)
+		if err != nil {
+			return nil, err
+		}
+
+		return getRecords(flattenrecords, records, fields)
+	}
+}
+
+func getRecords(
+	flattenRecords map[string]any, records []map[string]any, fields []string,
+) ([]common.ReadResultRow, error) {
+	data := make([]common.ReadResultRow, len(records))
+
+	for i, record := range records { // nolint:varnamelen
+		id, ok := record["id"].(map[string]any) // nolint:varnamelen
+		if !ok {
+			return nil, common.ErrEmptyRecordIdResponse
+		}
+
+		recordID, ok := id["record_id"].(string)
+		if !ok {
+			return nil, common.ErrEmptyRecordIdResponse
+		}
+
+		fieldRecord, ok := flattenRecords[recordID].(map[string]any)
+		if !ok {
+			return nil, common.ErrEmptyRecordIdResponse
+		}
+
+		data[i].Raw = record
+		data[i].Fields = common.ExtractLowercaseFieldsFromRaw(fields, fieldRecord)
+	}
+
+	return data, nil
+}
+
+func flattenRecord(arr *ajson.Node) (map[string]any, error) {
+	const keyValuesObject = "values"
+
+	values, err := jsonquery.New(arr).ObjectOptional(keyValuesObject)
+	if err != nil {
+		return nil, err
+	}
+
+	original, err := jsonquery.Convertor.ObjectToMap(arr)
+	if err != nil {
+		return nil, err
+	}
+
+	nested, err := jsonquery.Convertor.ObjectToMap(values)
+	if err != nil {
+		return nil, err
+	}
+
+	// values object must be removed.
+	delete(original, keyValuesObject)
+
+	// Fields from values are moved to the top level.
+	for key, value := range nested {
+		original[key] = value
+	}
+
+	return original, nil
+}
+
+func flattenRecords(arr []*ajson.Node) (map[string]any, error) {
+	flattenMap := make(map[string]any, 0)
+
+	for _, element := range arr {
+		flattenedRecord, err := flattenRecord(element)
+		if err != nil {
+			return nil, err
+		}
+
+		recordId, err := jsonquery.New(element, "id").StringRequired("record_id")
+		if err != nil {
+			return nil, err
+		}
+
+		flattenMap[recordId] = flattenedRecord
+	}
+
+	return flattenMap, nil
+}
diff --git a/providers/attio/read.go b/providers/attio/read.go
index 12373c2d..a439e9b5 100644
--- a/providers/attio/read.go
+++ b/providers/attio/read.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/amp-labs/connectors/common"
 	"github.com/amp-labs/connectors/common/urlbuilder"
+	"github.com/amp-labs/connectors/internal/datautils"
 )
 
 func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
@@ -13,10 +14,14 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 		return nil, err
 	}
 
-	if !supportedObjectsByRead.Has(config.ObjectName) {
-		return nil, common.ErrOperationNotSupportedForObject
+	if supportAttioApi.Has(config.ObjectName) {
+		return c.readAPI(ctx, config)
 	}
 
+	return c.readStandardOrCustomObject(ctx, config)
+}
+
+func (c *Connector) readAPI(ctx context.Context, config common.ReadParams) (*common.ReadResult, error) {
 	url, err := c.buildURL(config)
 	if err != nil {
 		return nil, err
@@ -30,12 +35,43 @@ func (c *Connector) Read(ctx context.Context, config common.ReadParams) (*common
 	return common.ParseResult(
 		rsp,
 		common.GetRecordsUnderJSONPath("data"),
-		makeNextRecordsURL(url),
+		makeNextRecordsURL(url, config.ObjectName),
 		common.GetMarshaledData,
 		config.Fields,
 	)
 }
 
+func (c *Connector) readStandardOrCustomObject(
+	ctx context.Context, config common.ReadParams,
+) (*common.ReadResult, error) {
+	// To handle standard/custom objects
+	url, err := c.getObjectReadURL(config.ObjectName)
+	if err != nil {
+		return nil, err
+	}
+
+	body := constructRequestBody(config)
+
+	offset := 0
+
+	if val, ok := body["offset"].(int); ok {
+		offset = val
+	}
+
+	rsp, err := c.Client.Post(ctx, url.String(), body)
+	if err != nil {
+		return nil, err
+	}
+
+	return common.ParseResult(
+		rsp,
+		common.GetRecordsUnderJSONPath("data"),
+		makeNextRecordStandardObj(offset),
+		DataMarshall(rsp),
+		config.Fields,
+	)
+}
+
 func (c *Connector) buildURL(config common.ReadParams) (*urlbuilder.URL, error) {
 	if len(config.NextPage) != 0 {
 		// Next page.
@@ -48,9 +84,45 @@ func (c *Connector) buildURL(config common.ReadParams) (*urlbuilder.URL, error)
 	}
 
 	if supportLimitAndOffset.Has(config.ObjectName) {
-		url.WithQueryParam("limit", strconv.Itoa(DefaultPageSize))
+		var pageSize int
+
+		if config.ObjectName == objectNameNotes {
+			pageSize = DefaultPageSizeForNotesObj
+		} else {
+			pageSize = DefaultPageSize
+		}
+
+		url.WithQueryParam("limit", strconv.Itoa(pageSize))
 		url.WithQueryParam("offset", "0")
 	}
 
 	return url, nil
 }
+
+// construct body params for filter the data using since field.
+func constructRequestBody(config common.ReadParams) map[string]any {
+	body := map[string]any{}
+
+	if len(config.NextPage) != 0 {
+		offset, err := strconv.Atoi(config.NextPage.String())
+		if err != nil {
+			return nil
+		}
+
+		body["offset"] = offset
+	}
+
+	if !config.Since.IsZero() {
+		filter := make(map[string]any)
+
+		filter["created_at"] = map[string]string{
+			"$gte": datautils.Time.FormatRFC3339inUTCWithMilliseconds(config.Since),
+		}
+
+		body["filter"] = filter
+	}
+
+	body["limit"] = DefaultPageSize
+
+	return body
+}
diff --git a/providers/attio/url.go b/providers/attio/url.go
new file mode 100644
index 00000000..be43e2f8
--- /dev/null
+++ b/providers/attio/url.go
@@ -0,0 +1,27 @@
+package attio
+
+import (
+	"github.com/amp-labs/connectors/common/urlbuilder"
+)
+
+// A trailing slash is added to the relative URL to ensure proper concatenation of dynamic values.
+//
+// Relative URL for retrieving metadata for standard and custom objects in Attio.
+func (c *Connector) getObjectAttributesURL(objName string) (*urlbuilder.URL, error) {
+	return urlbuilder.New(c.BaseURL, apiVersion, "objects", objName, "attributes")
+}
+
+// Relative URL for retrieving display Name for standard and custom objects in Attio.
+func (c *Connector) getObjectsURL(objName string) (*urlbuilder.URL, error) {
+	return urlbuilder.New(c.BaseURL, apiVersion, "objects", objName)
+}
+
+// Relative URL for retrieving the options in the attributes.
+func (c *Connector) getOptionsURL(objName, attributeID string) (*urlbuilder.URL, error) {
+	return urlbuilder.New(c.BaseURL, apiVersion, "objects", objName, "attributes", attributeID, "options")
+}
+
+// Relative URL for retrieving standard and custom object read URL.
+func (c *Connector) getObjectReadURL(objName string) (*urlbuilder.URL, error) {
+	return urlbuilder.New(c.BaseURL, apiVersion, "objects", objName, "records", "query")
+}


Test patch:
diff --git a/providers/attio/metadata_test.go b/providers/attio/metadata_test.go
index bc0e4147..7712152d 100644
--- a/providers/attio/metadata_test.go
+++ b/providers/attio/metadata_test.go
@@ -16,12 +16,15 @@ import (
 func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 	t.Parallel()
 
-	objectresponse := testutils.DataFromFile(t, "objects.json")
-	listresponse := testutils.DataFromFile(t, "lists.json")
-	notesresponse := testutils.DataFromFile(t, "notes.json")
-	workspacemembersresponse := testutils.DataFromFile(t, "workspace_members.json")
-	webhooksresponse := testutils.DataFromFile(t, "webhooks.json")
-	tasksresponse := testutils.DataFromFile(t, "tasks.json")
+	listResponse := testutils.DataFromFile(t, "lists.json")
+	notesResponse := testutils.DataFromFile(t, "notes.json")
+	workspacemembersResponse := testutils.DataFromFile(t, "workspace_members.json")
+	tasksResponse := testutils.DataFromFile(t, "tasks.json")
+	companiesResponse := testutils.DataFromFile(t, "companies.json")
+	companiesObjectResponse := []byte(`{"data": {"plural_noun": "Companies"}}`)
+	usersResponse := testutils.DataFromFile(t, "users.json")
+	optionsResponse := testutils.DataFromFile(t, "options.json")
+	usersObjectResponse := []byte(`{"data": {"plural_noun": "Users"}}`)
 
 	tests := []testroutines.Metadata{
 		{
@@ -31,100 +34,246 @@ func TestListObjectMetadata(t *testing.T) { // nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Successfully describe multiple object with metadata",
-			Input: []string{"objects", "lists", "workspace_members", "notes", "webhooks", "tasks"},
+			Input: []string{"lists", "workspace_members", "notes", "tasks", "companies", "users"},
 			Server: mockserver.Switch{
 				Setup: mockserver.ContentJSON(),
 				Cases: []mockserver.Case{{
-					If:   mockcond.PathSuffix("/v2/objects"),
-					Then: mockserver.Response(http.StatusOK, objectresponse),
-				}, {
 					If:   mockcond.PathSuffix("/v2/lists"),
-					Then: mockserver.Response(http.StatusOK, listresponse),
+					Then: mockserver.Response(http.StatusOK, listResponse),
 				}, {
 					If:   mockcond.PathSuffix("/v2/workspace_members"),
-					Then: mockserver.Response(http.StatusOK, workspacemembersresponse),
+					Then: mockserver.Response(http.StatusOK, workspacemembersResponse),
 				}, {
 					If:   mockcond.PathSuffix("/v2/notes"),
-					Then: mockserver.Response(http.StatusOK, notesresponse),
+					Then: mockserver.Response(http.StatusOK, notesResponse),
 				}, {
 					If:   mockcond.PathSuffix("/v2/tasks"),
-					Then: mockserver.Response(http.StatusOK, tasksresponse),
+					Then: mockserver.Response(http.StatusOK, tasksResponse),
+				}, {
+					If:   mockcond.PathSuffix("/v2/objects/companies/attributes"),
+					Then: mockserver.Response(http.StatusOK, companiesResponse),
+				}, {
+					If:   mockcond.PathSuffix("/v2/objects/companies"),
+					Then: mockserver.Response(http.StatusOK, companiesObjectResponse),
+				}, {
+					If:   mockcond.PathSuffix("/v2/objects/users/attributes"),
+					Then: mockserver.Response(http.StatusOK, usersResponse),
 				}, {
-					If:   mockcond.PathSuffix("/v2/webhooks"),
-					Then: mockserver.Response(http.StatusOK, webhooksresponse),
-				}},
+					If:   mockcond.PathSuffix("/v2/objects/users"),
+					Then: mockserver.Response(http.StatusOK, usersObjectResponse),
+				}, {
+					If:   mockcond.PathSuffix("/v2/objects/ffbca575-69c4-4080-bf98-91d79aeea4b1/attributes/89c07285-4d31-4fa7-9cbf-779c5f4debf1/options"),
+					Then: mockserver.Response(http.StatusOK, optionsResponse),
+				},
+				},
 			}.Server(),
 			Comparator: testroutines.ComparatorSubsetMetadata,
 			Expected: &common.ListObjectMetadataResult{
 				Result: map[string]common.ObjectMetadata{
-					"objects": {
-						DisplayName: "objects",
-						FieldsMap: map[string]string{
-							"api_slug":      "api_slug",
-							"created_at":    "created_at",
-							"id":            "id",
-							"plural_noun":   "plural_noun",
-							"singular_noun": "singular_noun",
-						},
-					},
 					"lists": {
 						DisplayName: "lists",
+						Fields: map[string]common.FieldMetadata{
+							"api_slug": {
+								DisplayName:  "api_slug",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"id": {
+								DisplayName:  "id",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"name": {
+								DisplayName:  "name",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+						},
 						FieldsMap: map[string]string{
-							"api_slug":                "api_slug",
-							"created_at":              "created_at",
-							"created_by_actor":        "created_by_actor",
-							"id":                      "id",
-							"name":                    "name",
-							"parent_object":           "parent_object",
-							"workspace_access":        "workspace_access",
-							"workspace_member_access": "workspace_member_access",
+							"api_slug": "api_slug",
+							"id":       "id",
+							"name":     "name",
 						},
 					},
 					"workspace_members": {
 						DisplayName: "workspace_members",
+						Fields: map[string]common.FieldMetadata{
+							"email_address": {
+								DisplayName:  "email_address",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"first_name": {
+								DisplayName:  "first_name",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"id": {
+								DisplayName:  "id",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"last_name": {
+								DisplayName:  "last_name",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+						},
 						FieldsMap: map[string]string{
-							"access_level":  "access_level",
-							"avatar_url":    "avatar_url",
-							"created_at":    "created_at",
 							"email_address": "email_address",
 							"first_name":    "first_name",
 							"id":            "id",
 							"last_name":     "last_name",
 						},
 					},
-					"webhooks": {
-						DisplayName: "webhooks",
-						FieldsMap: map[string]string{
-							"created_at":    "created_at",
-							"id":            "id",
-							"status":        "status",
-							"subscriptions": "subscriptions",
-							"target_url":    "target_url",
-						},
-					},
 					"notes": {
 						DisplayName: "notes",
+						Fields: map[string]common.FieldMetadata{
+							"content_plaintext": {
+								DisplayName:  "content_plaintext",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"id": {
+								DisplayName:  "id",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"title": {
+								DisplayName:  "title",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+						},
 						FieldsMap: map[string]string{
 							"content_plaintext": "content_plaintext",
-							"created_at":        "created_at",
-							"created_by_actor":  "created_by_actor",
 							"id":                "id",
-							"parent_object":     "parent_object",
-							"parent_record_id":  "parent_record_id",
 							"title":             "title",
 						},
 					},
 					"tasks": {
 						DisplayName: "tasks",
+						Fields: map[string]common.FieldMetadata{
+							"content_plaintext": {
+								DisplayName:  "content_plaintext",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"id": {
+								DisplayName:  "id",
+								ValueType:    "other",
+								ProviderType: "",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+						},
 						FieldsMap: map[string]string{
-							"assignees":         "assignees",
 							"content_plaintext": "content_plaintext",
-							"created_at":        "created_at",
-							"created_by_actor":  "created_by_actor",
-							"deadline_at":       "deadline_at",
 							"id":                "id",
-							"is_completed":      "is_completed",
-							"linked_records":    "linked_records",
+						},
+					},
+					"companies": {
+						DisplayName: "Companies",
+						Fields: map[string]common.FieldMetadata{
+							"record_id": {
+								DisplayName:  "record_id",
+								ValueType:    "string",
+								ProviderType: "text",
+								ReadOnly:     true,
+								Values:       nil,
+							},
+							"domains": {
+								DisplayName:  "domains",
+								ValueType:    "multiSelect",
+								ProviderType: "domain",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"name": {
+								DisplayName:  "name",
+								ValueType:    "string",
+								ProviderType: "text",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"created_at": {
+								DisplayName:  "created_at",
+								ValueType:    "datetime",
+								ProviderType: "timestamp",
+								ReadOnly:     true,
+								Values:       nil,
+							},
+							"created_by": {
+								DisplayName:  "created_by",
+								ValueType:    "other",
+								ProviderType: "actor-reference",
+								ReadOnly:     true,
+								Values:       nil,
+							},
+						},
+						FieldsMap: map[string]string{
+							"record_id":  "record_id",
+							"domains":    "domains",
+							"name":       "name",
+							"created_at": "created_at",
+							"created_by": "created_by",
+						},
+					},
+					"users": {
+						DisplayName: "Users",
+						Fields: map[string]common.FieldMetadata{
+							"record_id": {
+								DisplayName:  "record_id",
+								ValueType:    "string",
+								ProviderType: "text",
+								ReadOnly:     true,
+								Values:       nil,
+							},
+							"user_id": {
+								DisplayName:  "user_id",
+								ValueType:    "string",
+								ProviderType: "text",
+								ReadOnly:     false,
+								Values:       nil,
+							},
+							"education": {
+								DisplayName:  "education",
+								ValueType:    "multiSelect",
+								ProviderType: "select",
+								ReadOnly:     false,
+								Values: common.FieldValues{
+									{Value: "UG", DisplayValue: "UG"},
+									{Value: "PG", DisplayValue: "PG"},
+									{Value: "Diploma", DisplayValue: "Diploma"},
+								},
+							},
+						},
+						FieldsMap: map[string]string{
+							"record_id": "record_id",
+							"user_id":   "user_id",
+							"education": "education",
 						},
 					},
 				},
diff --git a/providers/attio/read_test.go b/providers/attio/read_test.go
index b7d577cb..bee0d453 100644
--- a/providers/attio/read_test.go
+++ b/providers/attio/read_test.go
@@ -4,6 +4,7 @@ package attio
 import (
 	"net/http"
 	"testing"
+	"time"
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
@@ -15,12 +16,11 @@ import (
 func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 	t.Parallel()
 
-	responseObjects := testutils.DataFromFile(t, "objects.json")
 	responseLists := testutils.DataFromFile(t, "lists.json")
 	responseWorkspace := testutils.DataFromFile(t, "workspace_members.json")
 	responseNotes := testutils.DataFromFile(t, "notes.json")
 	responseTasks := testutils.DataFromFile(t, "tasks.json")
-	responseWebhooks := testutils.DataFromFile(t, "webhooks.json")
+	responseCompanies := testutils.DataFromFile(t, "companies_read.json")
 
 	tests := []testroutines.Read{
 		{
@@ -28,67 +28,6 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrMissingObjects},
 		},
-		{
-			Name:         "At least one field is requested",
-			Input:        common.ReadParams{ObjectName: "objects"},
-			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{common.ErrMissingFields},
-		},
-		{
-			Name:         "Unknown objects are not supported",
-			Input:        common.ReadParams{ObjectName: "attributes", Fields: connectors.Fields("")},
-			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{common.ErrOperationNotSupportedForObject},
-		},
-		{
-			Name:  "An Empty response",
-			Input: common.ReadParams{ObjectName: "webhooks", Fields: connectors.Fields("")},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.ResponseString(http.StatusOK, `{"data":[]}`),
-			}.Server(),
-			Expected:     &common.ReadResult{Rows: 0, Data: []common.ReadResultRow{}, Done: true},
-			ExpectedErrs: nil,
-		},
-		{
-			Name:  "Read list of all objects",
-			Input: common.ReadParams{ObjectName: "objects", Fields: connectors.Fields("")},
-			Server: mockserver.Fixed{
-				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusOK, responseObjects),
-			}.Server(),
-			Expected: &common.ReadResult{
-				Rows: 2,
-				Data: []common.ReadResultRow{{
-					Fields: map[string]any{},
-					Raw: map[string]any{
-						"id": map[string]any{
-							"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-							"object_id":    "9aff8088-dd1f-4e98-ad76-b1e49b9a419a",
-						},
-						"api_slug":      "people",
-						"singular_noun": "Person",
-						"plural_noun":   "People",
-						"created_at":    "2024-09-17T11:41:18.736000000Z",
-					},
-				}, {
-					Fields: map[string]any{},
-					Raw: map[string]any{
-						"id": map[string]any{
-							"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-							"object_id":    "f4df082c-b46e-43e4-a747-f7918b487f44",
-						},
-						"api_slug":      "companies",
-						"singular_noun": "Company",
-						"plural_noun":   "Companies",
-						"created_at":    "2024-09-17T11:41:18.736000000Z",
-					},
-				}},
-				NextPage: "",
-				Done:     true,
-			},
-			ExpectedErrs: nil,
-		},
 		{
 			Name:  "Read list of all lists",
 			Input: common.ReadParams{ObjectName: "lists", Fields: connectors.Fields("")},
@@ -105,23 +44,8 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 							"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
 							"list_id":      "7ddc974a-2ab2-4a96-a83e-853eacb0329f",
 						},
-						"api_slug":         "sales_6",
-						"created_at":       "2024-09-25T13:10:33.302000000Z",
-						"name":             "Sales",
-						"workspace_access": nil,
-						"workspace_member_access": []interface{}{
-							map[string]any{
-								"level":               "full-access",
-								"workspace_member_id": "cc3821aa-f738-42c0-a739-7b6de755e5f1",
-							},
-						},
-						"parent_object": []any{
-							"companies",
-						},
-						"created_by_actor": map[string]any{
-							"type": "workspace-member",
-							"id":   "cc3821aa-f738-42c0-a739-7b6de755e5f1",
-						},
+						"api_slug": "sales_6",
+						"name":     "Sales",
 					},
 				},
 				},
@@ -138,7 +62,7 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 				Always: mockserver.Response(http.StatusOK, responseWorkspace),
 			}.Server(),
 			Expected: &common.ReadResult{
-				Rows: 2,
+				Rows: 1,
 				Data: []common.ReadResultRow{{
 					Fields: map[string]any{},
 					Raw: map[string]any{
@@ -148,24 +72,7 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 						},
 						"first_name":    "Integration",
 						"last_name":     "User",
-						"avatar_url":    nil,
 						"email_address": "integration.user@withampersand.com",
-						"access_level":  "admin",
-						"created_at":    "2024-09-17T11:41:21.779000000Z",
-					},
-				}, {
-					Fields: map[string]any{},
-					Raw: map[string]any{
-						"id": map[string]any{
-							"workspace_id":        "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-							"workspace_member_id": "cc3821aa-f738-42c0-a739-7b6de755e5f1",
-						},
-						"first_name":    "Sanjay Kanth",
-						"last_name":     "A",
-						"avatar_url":    "https://lh3.googleusercontent.com/a/ACg8ocL0Zwi9XArWL-GgYiUqPoDMKS1p1AQuQPPHVMQr0V023Ox_fYY=s96-c",
-						"email_address": "sanjaykanth.a@mitrahsoft.com",
-						"access_level":  "admin",
-						"created_at":    "2024-09-17T12:17:11.682000000Z",
 					},
 				}},
 				NextPage: "",
@@ -175,7 +82,7 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 		},
 		{
 			Name:  "Read list of all notes",
-			Input: common.ReadParams{ObjectName: "notes", Fields: connectors.Fields(""), NextPage: "test?limit=10"},
+			Input: common.ReadParams{ObjectName: "notes", Fields: connectors.Fields(""), NextPage: "test?limit=50"},
 			Server: mockserver.Fixed{
 				Setup:  mockserver.ContentJSON(),
 				Always: mockserver.Response(http.StatusOK, responseNotes),
@@ -189,26 +96,19 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 							"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
 							"note_id":      "32dc76ee-d094-40e1-b176-0f8e1b772f0a",
 						},
-						"parent_object":     "companies",
-						"parent_record_id":  "ec902ed9-aab7-4347-8e26-dca240ffba08",
 						"title":             "value",
 						"content_plaintext": "",
-						"created_by_actor": map[string]any{
-							"type": "workspace-member",
-							"id":   "cc3821aa-f738-42c0-a739-7b6de755e5f1",
-						},
-						"created_at": "2024-09-24T08:31:09.211000000Z",
 					},
 				},
 				},
-				NextPage: "test?limit=10&offset=10",
+				NextPage: "test?limit=50&offset=50",
 				Done:     false,
 			},
 			ExpectedErrs: nil,
 		},
 		{
 			Name:  "Read list of all tasks",
-			Input: common.ReadParams{ObjectName: "tasks", Fields: connectors.Fields(""), NextPage: "test?limit=10"},
+			Input: common.ReadParams{ObjectName: "tasks", Fields: connectors.Fields(""), NextPage: "test?limit=500"},
 			Server: mockserver.Fixed{
 				Setup:  mockserver.ContentJSON(),
 				Always: mockserver.Response(http.StatusOK, responseTasks),
@@ -223,65 +123,63 @@ func TestRead(t *testing.T) { // nolint:funlen,gocognit,cyclop
 							"task_id":      "4a585693-fa14-4ead-9e19-cc9251df59be",
 						},
 						"content_plaintext": "Follow up on current software solutions",
-						"is_completed":      false,
-						"deadline_at":       "2023-01-01T15:00:00.000000000Z",
-						"linked_records":    []any{},
-						"assignees": []interface{}{
-							map[string]any{
-								"referenced_actor_type": "workspace-member",
-								"referenced_actor_id":   "67af46e4-a450-4fee-a1d1-39729b3af771",
-							},
-						},
-						"created_by_actor": map[string]any{
-							"type": "api-token",
-							"id":   "53b1e97a-08d6-4d2e-856d-5371bb6f4052",
-						},
-						"created_at": "2024-09-23T11:04:07.647000000Z",
 					},
 				},
 				},
-				NextPage: "test?limit=10&offset=10",
+				NextPage: "test?limit=500&offset=500",
 				Done:     false,
 			},
 			ExpectedErrs: nil,
 		},
 		{
-			Name:  "Read list of all webhooks",
-			Input: common.ReadParams{ObjectName: "webhooks", Fields: connectors.Fields(""), NextPage: "test?limit=10"},
+			Name:  "Read list of all companies",
+			Input: common.ReadParams{ObjectName: "companies", Fields: connectors.Fields("name"), Since: time.Date(2025, 3, 1, 0, 0, 0, 0, time.UTC), NextPage: "test?limit=500"},
 			Server: mockserver.Fixed{
 				Setup:  mockserver.ContentJSON(),
-				Always: mockserver.Response(http.StatusOK, responseWebhooks),
+				Always: mockserver.Response(http.StatusOK, responseCompanies),
 			}.Server(),
 			Expected: &common.ReadResult{
 				Rows: 1,
 				Data: []common.ReadResultRow{{
-					Fields: map[string]any{},
+					Fields: map[string]any{
+						"name": []any{
+							map[string]any{
+								"active_from":  "2025-03-12T07:55:38.981000000Z",
+								"active_until": nil,
+								"created_by_actor": map[string]any{
+									"type": "system",
+									"id":   nil,
+								},
+								"value":          "Attio",
+								"attribute_type": "text",
+							},
+						},
+					},
 					Raw: map[string]any{
 						"id": map[string]any{
-							"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-							"webhook_id":   "02303005-d363-4f49-8fd2-19944003b1d2",
+							"workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+							"object_id":    "1fa986a6-952e-4e92-ba01-acca61a7b616",
+							"record_id":    "2db97cee-6c6b-4486-ae52-db8e4b6f44e9",
 						},
-						"target_url": "https://example.com/webhook",
-						"status":     "active",
-						"subscriptions": []interface{}{
-							map[string]any{
-								"event_type": "note.created",
-								"filter": map[string]any{
-									"$or": []interface{}{
-										map[string]any{
-											"field":    "id.list_id",
-											"operator": "equals",
-											"value":    "d34c5f6b-0410-4830-853d-8a6602252687",
-										},
+						"created_at": "2025-03-12T07:55:38.327000000Z",
+						"values": map[string]any{
+							"name": []any{
+								map[string]any{
+									"active_from":  "2025-03-12T07:55:38.981000000Z",
+									"active_until": nil,
+									"created_by_actor": map[string]any{
+										"type": "system",
+										"id":   nil,
 									},
+									"value":          "Attio",
+									"attribute_type": "text",
 								},
 							},
 						},
-						"created_at": "2024-09-20T11:14:17.403000000Z",
 					},
 				},
 				},
-				NextPage: "test?limit=10&offset=10",
+				NextPage: "500",
 				Done:     false,
 			},
 			ExpectedErrs: nil,
diff --git a/providers/attio/test/companies.json b/providers/attio/test/companies.json
new file mode 100644
index 00000000..33fa0a30
--- /dev/null
+++ b/providers/attio/test/companies.json
@@ -0,0 +1,66 @@
+{
+    "data": [
+      {
+        "id": {
+          "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+          "object_id": "1fa986a6-952e-4e92-ba01-acca61a7b616",
+          "attribute_id": "14447479-3006-4c0d-8855-61f3001c4990"
+        },
+        "api_slug": "record_id",
+        "type": "text",
+        "is_writable": false,
+        "is_multiselect": false,
+        "created_at": "2025-03-12T07:55:28.407000000Z"
+      },
+      {
+        "id": {
+          "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+          "object_id": "1fa986a6-952e-4e92-ba01-acca61a7b616",
+          "attribute_id": "c86ffb39-178e-48cd-a613-c125ee2f439f"
+        },
+        "api_slug": "domains",
+        "type": "domain",
+        "is_writable": true,
+        "is_multiselect": true,
+        "is_default_value_enabled": false,
+        "default_value":null,
+        "created_at": "2025-03-12T07:55:28.407000000Z"
+      },
+      {
+        "id": {
+          "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+          "object_id": "1fa986a6-952e-4e92-ba01-acca61a7b616",
+          "attribute_id": "3646a06e-28bb-444f-8335-1df342a4c1f4"
+        },
+        "api_slug": "name",
+        "type": "text",
+        "is_writable": true,
+        "is_multiselect": false,
+        "created_at": "2025-03-12T07:55:28.407000000Z"
+      },
+      {
+        "id": {
+          "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+          "object_id": "1fa986a6-952e-4e92-ba01-acca61a7b616",
+          "attribute_id": "548a073b-c739-46c7-878a-91739a2fac9c"
+        },
+        "api_slug": "created_at",
+        "type": "timestamp",
+        "is_writable": false,
+        "is_multiselect": false,
+        "created_at": "2025-03-12T07:55:28.407000000Z"
+      },
+      {
+        "id": {
+          "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+          "object_id": "1fa986a6-952e-4e92-ba01-acca61a7b616",
+          "attribute_id": "9a748587-a979-4682-9a35-5b97d2f6d478"
+        },
+        "api_slug": "created_by",
+        "type": "actor-reference",
+        "is_writable": false,
+        "is_multiselect": false,
+        "created_at": "2025-03-12T07:55:28.407000000Z"
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/providers/attio/test/companies_read.json b/providers/attio/test/companies_read.json
new file mode 100644
index 00000000..01ace794
--- /dev/null
+++ b/providers/attio/test/companies_read.json
@@ -0,0 +1,26 @@
+{
+    "data": [
+      {
+        "id": {
+          "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+          "object_id": "1fa986a6-952e-4e92-ba01-acca61a7b616",
+          "record_id": "2db97cee-6c6b-4486-ae52-db8e4b6f44e9"
+        },
+        "created_at": "2025-03-12T07:55:38.327000000Z",
+        "values": {
+          "name": [
+            {
+              "active_from": "2025-03-12T07:55:38.981000000Z",
+              "active_until": null,
+              "created_by_actor": {
+                "type": "system",
+                "id": null
+              },
+              "value": "Attio",
+              "attribute_type": "text"
+            }
+          ]
+        }
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/providers/attio/test/lists.json b/providers/attio/test/lists.json
index 316d7891..7f996936 100644
--- a/providers/attio/test/lists.json
+++ b/providers/attio/test/lists.json
@@ -6,22 +6,7 @@
                 "list_id": "7ddc974a-2ab2-4a96-a83e-853eacb0329f"
             },
             "api_slug": "sales_6",
-            "created_at": "2024-09-25T13:10:33.302000000Z",
-            "name": "Sales",
-            "workspace_access": null,
-            "workspace_member_access": [
-                {
-                    "level": "full-access",
-                    "workspace_member_id": "cc3821aa-f738-42c0-a739-7b6de755e5f1"
-                }
-            ],
-            "parent_object": [
-                "companies"
-            ],
-            "created_by_actor": {
-                "type": "workspace-member",
-                "id": "cc3821aa-f738-42c0-a739-7b6de755e5f1"
-            }
+            "name": "Sales"
         }
     ]
 }
\ No newline at end of file
diff --git a/providers/attio/test/notes.json b/providers/attio/test/notes.json
index 4e280652..73c176db 100644
--- a/providers/attio/test/notes.json
+++ b/providers/attio/test/notes.json
@@ -5,15 +5,8 @@
                 "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
                 "note_id": "32dc76ee-d094-40e1-b176-0f8e1b772f0a"
             },
-            "parent_object": "companies",
-            "parent_record_id": "ec902ed9-aab7-4347-8e26-dca240ffba08",
             "title": "value",
-            "content_plaintext": "",
-            "created_by_actor": {
-                "type": "workspace-member",
-                "id": "cc3821aa-f738-42c0-a739-7b6de755e5f1"
-            },
-            "created_at": "2024-09-24T08:31:09.211000000Z"
+            "content_plaintext": ""
         }
     ]
 }
\ No newline at end of file
diff --git a/providers/attio/test/objects.json b/providers/attio/test/objects.json
deleted file mode 100644
index 6e381ff9..00000000
--- a/providers/attio/test/objects.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-    "data": [
-        {
-            "id": {
-                "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-                "object_id": "9aff8088-dd1f-4e98-ad76-b1e49b9a419a"
-            },
-            "api_slug": "people",
-            "singular_noun": "Person",
-            "plural_noun": "People",
-            "created_at": "2024-09-17T11:41:18.736000000Z"
-        },
-        {
-            "id": {
-                "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-                "object_id": "f4df082c-b46e-43e4-a747-f7918b487f44"
-            },
-            "api_slug": "companies",
-            "singular_noun": "Company",
-            "plural_noun": "Companies",
-            "created_at": "2024-09-17T11:41:18.736000000Z"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/providers/attio/test/options.json b/providers/attio/test/options.json
new file mode 100644
index 00000000..1f53b8b8
--- /dev/null
+++ b/providers/attio/test/options.json
@@ -0,0 +1,16 @@
+{
+  "data": [
+    {
+      "title": "UG",
+      "is_archived": false
+    },
+    {
+      "title": "PG",
+      "is_archived": false
+    },
+    {
+      "title": "Diploma",
+      "is_archived": false
+    }
+  ]
+}
\ No newline at end of file
diff --git a/providers/attio/test/tasks.json b/providers/attio/test/tasks.json
index aa7ed4d3..a0038d5b 100644
--- a/providers/attio/test/tasks.json
+++ b/providers/attio/test/tasks.json
@@ -5,21 +5,7 @@
                 "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
                 "task_id": "4a585693-fa14-4ead-9e19-cc9251df59be"
             },
-            "content_plaintext": "Follow up on current software solutions",
-            "is_completed": false,
-            "deadline_at": "2023-01-01T15:00:00.000000000Z",
-            "linked_records": [],
-            "assignees": [
-                {
-                    "referenced_actor_type": "workspace-member",
-                    "referenced_actor_id": "67af46e4-a450-4fee-a1d1-39729b3af771"
-                }
-            ],
-            "created_by_actor": {
-                "type": "api-token",
-                "id": "53b1e97a-08d6-4d2e-856d-5371bb6f4052"
-            },
-            "created_at": "2024-09-23T11:04:07.647000000Z"
+            "content_plaintext": "Follow up on current software solutions"
         }
     ]
 }
\ No newline at end of file
diff --git a/providers/attio/test/users.json b/providers/attio/test/users.json
new file mode 100644
index 00000000..f393625f
--- /dev/null
+++ b/providers/attio/test/users.json
@@ -0,0 +1,60 @@
+{
+  "data": [
+    {
+      "id": {
+        "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+        "object_id": "ffbca575-69c4-4080-bf98-91d79aeea4b1",
+        "attribute_id": "06826311-cf4c-4c4b-a1fa-f14ce70d0004"
+      },
+      "title": "Record ID",
+      "api_slug": "record_id",
+      "type": "text",
+      "is_writable": false,
+      "is_multiselect": false,
+      "is_default_value_enabled": false
+    },
+    {
+      "id": {
+        "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+        "object_id": "ffbca575-69c4-4080-bf98-91d79aeea4b1",
+        "attribute_id": "529eea7a-2c2b-4d89-b2f7-acd3b3c6a719"
+      },
+      "title": "User ID",
+      "api_slug": "user_id",
+      "type": "text",
+      "is_writable": true,
+      "is_multiselect": false,
+      "is_default_value_enabled": false
+    },
+    {
+      "id": {
+        "workspace_id": "63d34516-b287-4c27-9d28-fe2adbebcd50",
+        "object_id": "ffbca575-69c4-4080-bf98-91d79aeea4b1",
+        "attribute_id": "89c07285-4d31-4fa7-9cbf-779c5f4debf1"
+      },
+      "title": "Education",
+      "api_slug": "education",
+      "type": "select",
+      "is_writable": true,
+      "is_multiselect": true,
+      "is_default_value_enabled": true,
+      "default_value": {
+        "type": "static",
+        "template": [
+          {
+            "option": "4ee3b77e-ef94-46b1-b76f-357482703388",
+            "attribute_type": "select"
+          },
+          {
+            "option": "2e41af64-8786-4ccf-9331-a5f8bfd17025",
+            "attribute_type": "select"
+          },
+          {
+            "option": "049df4b9-adf7-4f38-ae5c-9abf2abfadb8",
+            "attribute_type": "select"
+          }
+        ]
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/providers/attio/test/webhooks.json b/providers/attio/test/webhooks.json
deleted file mode 100644
index 88566a1e..00000000
--- a/providers/attio/test/webhooks.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
-    "data": [
-        {
-            "id": {
-                "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-                "webhook_id": "02303005-d363-4f49-8fd2-19944003b1d2"
-            },
-            "target_url": "https://example.com/webhook",
-            "status": "active",
-            "subscriptions": [
-                {
-                    "event_type": "note.created",
-                    "filter": {
-                        "$or": [
-                            {
-                                "field": "id.list_id",
-                                "operator": "equals",
-                                "value": "d34c5f6b-0410-4830-853d-8a6602252687"
-                            }
-                        ]
-                    }
-                }
-            ],
-            "created_at": "2024-09-20T11:14:17.403000000Z"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/providers/attio/test/workspace_members.json b/providers/attio/test/workspace_members.json
index 657b85d6..c835e430 100644
--- a/providers/attio/test/workspace_members.json
+++ b/providers/attio/test/workspace_members.json
@@ -7,22 +7,7 @@
             },
             "first_name": "Integration",
             "last_name": "User",
-            "avatar_url": null,
-            "email_address": "integration.user@withampersand.com",
-            "access_level": "admin",
-            "created_at": "2024-09-17T11:41:21.779000000Z"
-        },
-        {
-            "id": {
-                "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-                "workspace_member_id": "cc3821aa-f738-42c0-a739-7b6de755e5f1"
-            },
-            "first_name": "Sanjay Kanth",
-            "last_name": "A",
-            "avatar_url": "https://lh3.googleusercontent.com/a/ACg8ocL0Zwi9XArWL-GgYiUqPoDMKS1p1AQuQPPHVMQr0V023Ox_fYY=s96-c",
-            "email_address": "sanjaykanth.a@mitrahsoft.com",
-            "access_level": "admin",
-            "created_at": "2024-09-17T12:17:11.682000000Z"
+            "email_address": "integration.user@withampersand.com"
         }
     ]
 }
\ No newline at end of file
diff --git a/providers/attio/test/write_objects.json b/providers/attio/test/write_objects.json
deleted file mode 100644
index 49b3ab08..00000000
--- a/providers/attio/test/write_objects.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "data": {
-  "api_slug": "deal",
-  "created_at": "2024-10-04T10:01:42.878000000Z",        
-  "id": {
-   "object_id": "bf012982-06a9-47f7-9e87-07dc4945d502",  
-   "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6"
-  },
-  "plural_noun": "Dealss",
-  "singular_noun": "Deals"
- }
-}
\ No newline at end of file
diff --git a/providers/attio/test/write_webhook.json b/providers/attio/test/write_webhook.json
deleted file mode 100644
index a42f6c91..00000000
--- a/providers/attio/test/write_webhook.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-    "data": {
-    "created_at": "2024-10-04T10:05:01.173000000Z",
-    "id": {
-     "webhook_id": "7e5209b8-bd4e-41d9-bbcd-2f9bab7d4030",
-     "workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6"
-    },
-    "secret": "edc47acc750ba7af0b1663f606b3a3a5f6fb0a3c37f7b679c9416318b523d968",
-    "status": "active",
-    "subscriptions": [
-     {
-      "event_type": "note.deleted",
-      "filter": null
-     }
-    ],
-    "target_url": "https://f87a-117-216-131-16.ngrok-free.app"
-   }
-}
\ No newline at end of file
diff --git a/providers/attio/write_test.go b/providers/attio/write_test.go
index 669a5bae..50d3e774 100644
--- a/providers/attio/write_test.go
+++ b/providers/attio/write_test.go
@@ -16,23 +16,15 @@ import (
 func TestWrite(t *testing.T) { // nolint:funlen,gocognit,cyclop
 	t.Parallel()
 
-	responseObj := testutils.DataFromFile(t, "write_objects.json")
 	listResponse := testutils.DataFromFile(t, "write_lists.json")
 	notesresponse := testutils.DataFromFile(t, "write_notes.json")
 	tasksResponse := testutils.DataFromFile(t, "write_tasks.json")
-	webhookResponse := testutils.DataFromFile(t, "write_webhook.json")
 	tests := []testroutines.Write{
 		{
 			Name:         "Write object must be included",
 			Server:       mockserver.Dummy(),
 			ExpectedErrs: []error{common.ErrMissingObjects},
 		},
-		{
-			Name:         "Write needs data payload",
-			Input:        common.WriteParams{ObjectName: "objects"},
-			Server:       mockserver.Dummy(),
-			ExpectedErrs: []error{common.ErrMissingRecordData},
-		},
 		{
 			Name:     "Unknown object name is not supported",
 			Input:    common.WriteParams{ObjectName: "attributes", RecordData: "dummy"},
@@ -42,56 +34,6 @@ func TestWrite(t *testing.T) { // nolint:funlen,gocognit,cyclop
 				common.ErrOperationNotSupportedForObject,
 			},
 		},
-		{
-			Name:  "Create objects as POST",
-			Input: common.WriteParams{ObjectName: "objects", RecordData: "dummy"},
-			Server: mockserver.Conditional{
-				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPOST(),
-				Then:  mockserver.Response(http.StatusOK, responseObj),
-			}.Server(),
-			Expected: &common.WriteResult{
-				Success:  true,
-				RecordId: "bf012982-06a9-47f7-9e87-07dc4945d502",
-				Errors:   nil,
-				Data: map[string]any{
-					"api_slug":   "deal",
-					"created_at": "2024-10-04T10:01:42.878000000Z",
-					"id": map[string]any{
-						"object_id":    "bf012982-06a9-47f7-9e87-07dc4945d502",
-						"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-					},
-					"plural_noun":   "Dealss",
-					"singular_noun": "Deals",
-				},
-			},
-			ExpectedErrs: nil,
-		},
-		{
-			Name:  "Update objects as PATCH",
-			Input: common.WriteParams{ObjectName: "objects", RecordId: "bf012982-06a9-47f7-9e87-07dc4945d502", RecordData: "dummy"},
-			Server: mockserver.Conditional{
-				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPATCH(),
-				Then:  mockserver.Response(http.StatusOK, responseObj),
-			}.Server(),
-			Expected: &common.WriteResult{
-				Success:  true,
-				RecordId: "bf012982-06a9-47f7-9e87-07dc4945d502",
-				Errors:   nil,
-				Data: map[string]any{
-					"api_slug":   "deal",
-					"created_at": "2024-10-04T10:01:42.878000000Z",
-					"id": map[string]any{
-						"object_id":    "bf012982-06a9-47f7-9e87-07dc4945d502",
-						"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-					},
-					"plural_noun":   "Dealss",
-					"singular_noun": "Deals",
-				},
-			},
-			ExpectedErrs: nil,
-		},
 		{
 			Name:  "Create lists as POST",
 			Input: common.WriteParams{ObjectName: "lists", RecordData: "dummy"},
@@ -280,68 +222,6 @@ func TestWrite(t *testing.T) { // nolint:funlen,gocognit,cyclop
 			},
 			ExpectedErrs: nil,
 		},
-		{
-			Name:  "Create webhooks as POST",
-			Input: common.WriteParams{ObjectName: "webhooks", RecordData: "dummy"},
-			Server: mockserver.Conditional{
-				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPOST(),
-				Then:  mockserver.Response(http.StatusOK, webhookResponse),
-			}.Server(),
-			Expected: &common.WriteResult{
-				Success:  true,
-				RecordId: "7e5209b8-bd4e-41d9-bbcd-2f9bab7d4030",
-				Errors:   nil,
-				Data: map[string]any{
-					"created_at": "2024-10-04T10:05:01.173000000Z",
-					"id": map[string]any{
-						"webhook_id":   "7e5209b8-bd4e-41d9-bbcd-2f9bab7d4030",
-						"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-					},
-					"secret": "edc47acc750ba7af0b1663f606b3a3a5f6fb0a3c37f7b679c9416318b523d968",
-					"status": "active",
-					"subscriptions": []any{
-						map[string]any{
-							"event_type": "note.deleted",
-							"filter":     nil,
-						},
-					},
-					"target_url": "https://f87a-117-216-131-16.ngrok-free.app",
-				},
-			},
-			ExpectedErrs: nil,
-		},
-		{
-			Name:  "Update webhooks as PATCH",
-			Input: common.WriteParams{ObjectName: "webhooks", RecordId: "7e5209b8-bd4e-41d9-bbcd-2f9bab7d4030", RecordData: "dummy"},
-			Server: mockserver.Conditional{
-				Setup: mockserver.ContentJSON(),
-				If:    mockcond.MethodPATCH(),
-				Then:  mockserver.Response(http.StatusOK, webhookResponse),
-			}.Server(),
-			Expected: &common.WriteResult{
-				Success:  true,
-				RecordId: "7e5209b8-bd4e-41d9-bbcd-2f9bab7d4030",
-				Errors:   nil,
-				Data: map[string]any{
-					"created_at": "2024-10-04T10:05:01.173000000Z",
-					"id": map[string]any{
-						"webhook_id":   "7e5209b8-bd4e-41d9-bbcd-2f9bab7d4030",
-						"workspace_id": "0d4d7fa2-d6e8-4a61-a7dc-e178405ff3c6",
-					},
-					"secret": "edc47acc750ba7af0b1663f606b3a3a5f6fb0a3c37f7b679c9416318b523d968",
-					"status": "active",
-					"subscriptions": []any{
-						map[string]any{
-							"event_type": "note.deleted",
-							"filter":     nil,
-						},
-					},
-					"target_url": "https://f87a-117-216-131-16.ngrok-free.app",
-				},
-			},
-			ExpectedErrs: nil,
-		},
 	}
 
 	for _, tt := range tests {
diff --git a/test/attio/metadata/metadata.go b/test/attio/metadata/metadata.go
index ea5d8c5a..a7a48c3c 100644
--- a/test/attio/metadata/metadata.go
+++ b/test/attio/metadata/metadata.go
@@ -13,7 +13,7 @@ func main() {
 
 	conn := attio.GetAttioConnector(ctx)
 
-	m, err := conn.ListObjectMetadata(ctx, []string{"objects", "lists", "workspace_members", "webhooks", "tasks", "notes"})
+	m, err := conn.ListObjectMetadata(ctx, []string{"lists", "workspace_members", "tasks", "notes", "companies"})
 	if err != nil {
 		utils.Fail(err.Error())
 	}
diff --git a/test/attio/read/main.go b/test/attio/read/main.go
index 0a5033f0..bd9eeac2 100644
--- a/test/attio/read/main.go
+++ b/test/attio/read/main.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"time"
 
 	"github.com/amp-labs/connectors"
 	"github.com/amp-labs/connectors/common"
@@ -20,12 +21,7 @@ func main() {
 func MainFn() int {
 	conn := attio.GetAttioConnector(context.Background())
 
-	err := testReadObjects(context.Background(), conn)
-	if err != nil {
-		return 1
-	}
-
-	err = testReadLists(context.Background(), conn)
+	err := testReadLists(context.Background(), conn)
 	if err != nil {
 		return 1
 	}
@@ -35,11 +31,6 @@ func MainFn() int {
 		return 1
 	}
 
-	err = testReadWebhooks(context.Background(), conn)
-	if err != nil {
-		return 1
-	}
-
 	err = testReadTasks(context.Background(), conn)
 	if err != nil {
 		return 1
@@ -50,30 +41,12 @@ func MainFn() int {
 		return 1
 	}
 
-	return 0
-}
-
-func testReadObjects(ctx context.Context, conn *ap.Connector) error {
-	params := common.ReadParams{
-		ObjectName: "objects",
-		Fields:     connectors.Fields(""),
-	}
-
-	res, err := conn.Read(ctx, params)
-	if err != nil {
-		log.Fatal(err.Error())
-	}
-
-	// Print the results.
-	jsonStr, err := json.MarshalIndent(res, "", "  ")
+	err = testReadCompanies(context.Background(), conn)
 	if err != nil {
-		return fmt.Errorf("error marshalling JSON: %w", err)
+		return 1
 	}
 
-	_, _ = os.Stdout.Write(jsonStr)
-	_, _ = os.Stdout.WriteString("\n")
-
-	return nil
+	return 0
 }
 
 func testReadLists(ctx context.Context, conn *ap.Connector) error {
@@ -122,9 +95,9 @@ func testReadWorkspacemembers(ctx context.Context, conn *ap.Connector) error {
 	return nil
 }
 
-func testReadWebhooks(ctx context.Context, conn *ap.Connector) error {
+func testReadTasks(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
-		ObjectName: "webhooks",
+		ObjectName: "tasks",
 		Fields:     connectors.Fields(""),
 	}
 
@@ -145,9 +118,9 @@ func testReadWebhooks(ctx context.Context, conn *ap.Connector) error {
 	return nil
 }
 
-func testReadTasks(ctx context.Context, conn *ap.Connector) error {
+func testReadNotes(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
-		ObjectName: "tasks",
+		ObjectName: "notes",
 		Fields:     connectors.Fields(""),
 	}
 
@@ -168,10 +141,11 @@ func testReadTasks(ctx context.Context, conn *ap.Connector) error {
 	return nil
 }
 
-func testReadNotes(ctx context.Context, conn *ap.Connector) error {
+func testReadCompanies(ctx context.Context, conn *ap.Connector) error {
 	params := common.ReadParams{
-		ObjectName: "notes",
-		Fields:     connectors.Fields(""),
+		ObjectName: "companies",
+		Fields:     connectors.Fields("name"),
+		Since:      time.Date(2025, 3, 18, 0, 0, 0, 0, time.UTC),
 	}
 
 	res, err := conn.Read(ctx, params)
diff --git a/test/attio/write/main.go b/test/attio/write/main.go
index 5bb3cf7f..4416c1c8 100644
--- a/test/attio/write/main.go
+++ b/test/attio/write/main.go
@@ -24,12 +24,7 @@ func main() {
 func MainFn() int {
 	ctx := context.Background()
 
-	err := testObjects(ctx)
-	if err != nil {
-		return 1
-	}
-
-	err = testLists(ctx)
+	err := testLists(ctx)
 	if err != nil {
 		return 1
 	}
@@ -44,68 +39,9 @@ func MainFn() int {
 		return 1
 	}
 
-	err = testWebhooks(ctx)
-	if err != nil {
-		return 1
-	}
-
 	return 0
 }
 
-func testObjects(ctx context.Context) error {
-	conn := attio.GetAttioConnector(ctx)
-
-	slog.Info("Creating the object")
-
-	params := common.WriteParams{
-		ObjectName: "objects",
-		RecordData: map[string]interface{}{
-			"data": map[string]string{
-				"api_slug":      "deal",
-				"singular_noun": "Deals",
-				"plural_noun":   "Dealss",
-			},
-		},
-		RecordId: "",
-	}
-
-	writeRes, err := Write(ctx, conn, params)
-	if err != nil {
-		fmt.Println("ERR: ", err)
-
-		return err
-	}
-
-	if err := constructResponse(writeRes); err != nil {
-		return err
-	}
-
-	slog.Info("Updating the object")
-
-	updateparams := common.WriteParams{
-		ObjectName: "objects",
-		RecordData: map[string]interface{}{
-			"data": map[string]string{
-				"singular_noun": "Deal",
-			},
-		},
-		RecordId: writeRes.Data["id"].(map[string]interface{})["object_id"].(string),
-	}
-
-	updateres, err := Write(ctx, conn, updateparams)
-	if err != nil {
-		fmt.Println("ERR: ", err)
-
-		return err
-	}
-
-	if err := constructResponse(updateres); err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func testLists(ctx context.Context) error {
 	conn := attio.GetAttioConnector(ctx)
 
@@ -270,69 +206,6 @@ func testTasks(ctx context.Context) error {
 	return nil
 }
 
-func testWebhooks(ctx context.Context) error {
-	conn := attio.GetAttioConnector(ctx)
-
-	slog.Info("Creating the webhooks")
-
-	writeParams := common.WriteParams{
-		ObjectName: "webhooks",
-		RecordData: map[string]any{
-			"data": map[string]any{
-				"target_url": "https://f87a-117-216-131-16.ngrok-free.app",
-				"subscriptions": []map[string]any{
-					{
-						"event_type": "note.deleted",
-						"filter":     nil,
-					},
-				},
-			},
-		},
-		RecordId: "",
-	}
-
-	writeRes, err := Write(ctx, conn, writeParams)
-	if err != nil {
-		fmt.Println("ERR: ", err)
-
-		return err
-	}
-
-	if err := constructResponse(writeRes); err != nil {
-		return err
-	}
-
-	slog.Info("Updating the webhooks")
-
-	updateParams := common.WriteParams{
-		ObjectName: "webhooks",
-		RecordData: map[string]any{
-			"data": map[string]any{
-				"subscriptions": []map[string]any{
-					{
-						"event_type": "note.created",
-						"filter":     nil,
-					},
-				},
-			},
-		},
-		RecordId: writeRes.Data["id"].(map[string]interface{})["webhook_id"].(string),
-	}
-
-	writeres, err := Write(ctx, conn, updateParams)
-	if err != nil {
-		fmt.Println("ERR: ", err)
-
-		return err
-	}
-
-	if err := constructResponse(writeres); err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func Write(ctx context.Context, conn *ap.Connector, payload common.WriteParams) (*common.WriteResult, error) {
 	res, err := conn.Write(ctx, payload)
 	if err != nil {