attio 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "attio"
5
+ require "time"
6
+
7
+ # Example: Complete CRM Workflow
8
+ #
9
+ # This example demonstrates a complete CRM workflow including:
10
+ # - Creating and managing companies and contacts
11
+ # - Working with lists and workspace management
12
+ # - Using collaboration features
13
+ # - Error handling
14
+
15
+ # Initialize the client
16
+ client = Attio.client(api_key: ENV.fetch("ATTIO_API_KEY"))
17
+
18
+ puts "Attio Complete CRM Workflow Example"
19
+ puts "=" * 40
20
+
21
+ # Error handling wrapper
22
+ def safe_execute(description)
23
+ print "#{description}..."
24
+ result = yield
25
+ puts " ✓"
26
+ result
27
+ rescue Attio::NotFoundError => e
28
+ puts " ✗ Not found: #{e.message}"
29
+ nil
30
+ rescue Attio::ValidationError => e
31
+ puts " ✗ Validation error: #{e.message}"
32
+ nil
33
+ rescue Attio::Error => e
34
+ puts " ✗ Error: #{e.message}"
35
+ nil
36
+ end
37
+
38
+ # 1. Get workspace information
39
+ puts "\n1. Workspace Information"
40
+ puts "-" * 20
41
+ workspace = safe_execute("Getting current workspace") do
42
+ client.workspaces.get
43
+ end
44
+
45
+ if workspace
46
+ puts " Workspace: #{workspace.dig('data', 'name')}"
47
+ puts " ID: #{workspace.dig('data', 'id', 'workspace_id')}"
48
+ end
49
+
50
+ # 2. List available objects and attributes
51
+ puts "\n2. Schema Information"
52
+ puts "-" * 20
53
+ objects = safe_execute("Listing objects") do
54
+ client.objects.list
55
+ end
56
+
57
+ if objects && objects["data"]
58
+ puts " Available objects:"
59
+ objects["data"].each do |obj|
60
+ puts " - #{obj['api_slug']}: #{obj['name']}"
61
+ end
62
+ end
63
+
64
+ # 3. Create a sales pipeline workflow
65
+ puts "\n3. Sales Pipeline Setup"
66
+ puts "-" * 20
67
+
68
+ # Create a company
69
+ company = safe_execute("Creating prospect company") do
70
+ client.records.create(
71
+ object: "companies",
72
+ data: {
73
+ name: "Innovation Labs Inc",
74
+ domain: "innovationlabs.example.com",
75
+ industry: "Technology",
76
+ description: "Potential enterprise customer",
77
+ }
78
+ )
79
+ end
80
+ company_id = company&.dig("data", "id", "record_id")
81
+
82
+ # Create primary contact
83
+ contact = safe_execute("Creating primary contact") do
84
+ client.records.create(
85
+ object: "people",
86
+ data: {
87
+ name: "Michael Chen",
88
+ email: "m.chen@innovationlabs.example.com",
89
+ title: "CTO",
90
+ phone: "+1-555-0123",
91
+ }
92
+ )
93
+ end
94
+ contact&.dig("data", "id", "record_id")
95
+
96
+ # Create additional contacts
97
+ if company_id
98
+ safe_execute("Creating additional contact") do
99
+ client.records.create(
100
+ object: "people",
101
+ data: {
102
+ name: "Lisa Park",
103
+ email: "l.park@innovationlabs.example.com",
104
+ title: "VP of Product",
105
+ }
106
+ )
107
+ end
108
+ end
109
+
110
+ # 4. Create and manage lists
111
+ puts "\n4. List Management"
112
+ puts "-" * 20
113
+
114
+ lists = safe_execute("Getting available lists") do
115
+ client.lists.list
116
+ end
117
+
118
+ if lists && lists["data"] && !lists["data"].empty?
119
+ list_id = lists["data"].first["id"]["list_id"]
120
+
121
+ # Add company to a list
122
+ if company_id
123
+ safe_execute("Adding company to list") do
124
+ client.lists.create_entry(
125
+ id: list_id,
126
+ data: {
127
+ record_id: company_id,
128
+ notes: "High priority prospect",
129
+ }
130
+ )
131
+ end
132
+ end
133
+
134
+ # Get list entries
135
+ entries = safe_execute("Getting list entries") do
136
+ client.lists.entries(id: list_id, limit: 5)
137
+ end
138
+
139
+ puts " List has #{entries['data']&.length || 0} entries" if entries
140
+ end
141
+
142
+ # 5. Collaboration workflow
143
+ puts "\n5. Collaboration Workflow"
144
+ puts "-" * 20
145
+
146
+ if company_id
147
+ # Create a discussion thread
148
+ thread = safe_execute("Creating sales discussion thread") do
149
+ client.threads.create(
150
+ parent_object: "companies",
151
+ parent_record_id: company_id,
152
+ title: "Sales Opportunity - Innovation Labs",
153
+ description: "Track all sales activities and discussions"
154
+ )
155
+ end
156
+ thread_id = thread&.dig("data", "id", "thread_id")
157
+
158
+ # Add initial comment
159
+ if thread_id
160
+ safe_execute("Adding sales strategy comment") do
161
+ client.comments.create(
162
+ thread_id: thread_id,
163
+ content: <<~CONTENT
164
+ ## Opportunity Overview
165
+ - **Company**: Innovation Labs Inc
166
+ - **Potential Value**: $100K ARR
167
+ - **Timeline**: Q1 2025
168
+
169
+ ## Next Steps
170
+ 1. Schedule discovery call
171
+ 2. Prepare custom demo
172
+ 3. Send pricing proposal
173
+ CONTENT
174
+ )
175
+ end
176
+ end
177
+
178
+ # Create tasks
179
+ safe_execute("Creating discovery call task") do
180
+ client.tasks.create(
181
+ parent_object: "companies",
182
+ parent_record_id: company_id,
183
+ title: "Schedule discovery call with Michael Chen",
184
+ due_date: (Date.today + 3).iso8601,
185
+ description: "Initial discovery call to understand their requirements"
186
+ )
187
+ end
188
+
189
+ safe_execute("Creating demo prep task") do
190
+ client.tasks.create(
191
+ parent_object: "companies",
192
+ parent_record_id: company_id,
193
+ title: "Prepare customized product demo",
194
+ due_date: (Date.today + 7).iso8601
195
+ )
196
+ end
197
+
198
+ # Create meeting notes
199
+ safe_execute("Creating initial contact notes") do
200
+ client.notes.create(
201
+ parent_object: "companies",
202
+ parent_record_id: company_id,
203
+ title: "Initial Contact - #{Date.today}",
204
+ content: <<~CONTENT
205
+ ## Contact Method
206
+ Inbound inquiry via website
207
+
208
+ ## Requirements
209
+ - Looking for enterprise CRM solution
210
+ - Team size: 200+ employees
211
+ - Current solution: Spreadsheets
212
+
213
+ ## Pain Points
214
+ - No centralized customer data
215
+ - Manual reporting processes
216
+ - Limited collaboration features
217
+
218
+ ## Budget
219
+ Approved budget: $100-150K annually
220
+
221
+ ## Decision Timeline
222
+ Looking to implement in Q1 2025
223
+ CONTENT
224
+ )
225
+ end
226
+ end
227
+
228
+ # 6. Query and reporting
229
+ puts "\n6. Queries and Reporting"
230
+ puts "-" * 20
231
+
232
+ # Get all high-priority tasks
233
+ tasks = safe_execute("Getting pending tasks") do
234
+ client.tasks.list(status: "pending", limit: 5)
235
+ end
236
+
237
+ if tasks
238
+ puts " Pending tasks: #{tasks['data']&.length || 0}"
239
+ tasks["data"]&.each do |task|
240
+ puts " - #{task['title']}"
241
+ end
242
+ end
243
+
244
+ # Query recent companies
245
+ recent_companies = safe_execute("Finding recent companies") do
246
+ client.records.list(
247
+ object: "companies",
248
+ sorts: [{ field: "created_at", direction: "desc" }],
249
+ limit: 5
250
+ )
251
+ end
252
+
253
+ puts " Recent companies: #{recent_companies['data']&.length || 0}" if recent_companies
254
+
255
+ # 7. User management
256
+ puts "\n7. User Information"
257
+ puts "-" * 20
258
+
259
+ # Get current user
260
+ me = safe_execute("Getting current user") do
261
+ client.users.me
262
+ end
263
+
264
+ if me
265
+ puts " Current user: #{me.dig('data', 'name')}"
266
+ puts " Email: #{me.dig('data', 'email')}"
267
+ end
268
+
269
+ # List all users
270
+ users = safe_execute("Listing workspace users") do
271
+ client.users.list
272
+ end
273
+
274
+ puts " Total users: #{users['data']&.length || 0}" if users
275
+
276
+ # 8. Advanced features
277
+ puts "\n8. Advanced Features"
278
+ puts "-" * 20
279
+
280
+ # Get custom attributes
281
+ if company_id
282
+ attributes = safe_execute("Getting company attributes") do
283
+ client.attributes.list(object: "companies")
284
+ end
285
+
286
+ puts " Company attributes: #{attributes['data']&.length || 0}" if attributes
287
+ end
288
+
289
+ # Demonstrate pagination
290
+ puts " Demonstrating pagination..."
291
+ all_records = []
292
+ cursor = nil
293
+ pages = 0
294
+
295
+ loop do
296
+ params = { object: "companies", limit: 2 }
297
+ params[:cursor] = cursor if cursor
298
+
299
+ page = safe_execute(" Fetching page #{pages + 1}") do
300
+ client.records.list(**params)
301
+ end
302
+
303
+ break unless page && page["data"]
304
+
305
+ all_records.concat(page["data"])
306
+ pages += 1
307
+
308
+ cursor = page.dig("pagination", "next_cursor")
309
+ break unless cursor && pages < 3 # Limit to 3 pages for demo
310
+ end
311
+
312
+ puts " Fetched #{all_records.length} records across #{pages} pages"
313
+
314
+ # 9. Summary
315
+ puts "\n9. Workflow Summary"
316
+ puts "-" * 20
317
+ puts " ✓ Workspace configured"
318
+ puts " ✓ Company and contacts created"
319
+ puts " ✓ Lists managed"
320
+ puts " ✓ Collaboration tools utilized"
321
+ puts " ✓ Tasks and notes created"
322
+ puts " ✓ Queries executed"
323
+ puts " ✓ User information retrieved"
324
+
325
+ puts "\n#{'=' * 40}"
326
+ puts "Complete workflow example finished!"
327
+ puts "\nThis example demonstrated:"
328
+ puts " • Complete CRM setup"
329
+ puts " • Record creation and management"
330
+ puts " • List operations"
331
+ puts " • Collaboration features"
332
+ puts " • Task management"
333
+ puts " • Querying and reporting"
334
+ puts " • User management"
335
+ puts " • Error handling"
336
+ puts " • Pagination"
337
+
338
+ # Optional cleanup
339
+ # if company_id
340
+ # safe_execute("Cleaning up company") do
341
+ # client.records.delete(object: "companies", id: company_id)
342
+ # end
343
+ # end
344
+ # if contact_id
345
+ # safe_execute("Cleaning up contact") do
346
+ # client.records.delete(object: "people", id: contact_id)
347
+ # end
348
+ # end
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "attio"
6
+ require "date"
7
+
8
+ # Example demonstrating Notes and Tasks functionality
9
+ #
10
+ # This example shows how to:
11
+ # - Create and manage notes on records
12
+ # - Create and manage tasks
13
+ # - Work with task assignments and due dates
14
+ # - Track task completion
15
+
16
+ client = Attio.client(api_key: ENV.fetch("ATTIO_API_KEY"))
17
+
18
+ puts "📓 Attio Notes and Tasks Example"
19
+ puts "=" * 50
20
+
21
+ begin
22
+ # First, find or create a person to work with
23
+ puts "\n👤 Setting up test person..."
24
+ test_person = client.records.create(
25
+ object: "people",
26
+ data: {
27
+ name: "Jane Smith",
28
+ email: "jane.smith@example.com",
29
+ title: "Product Manager",
30
+ }
31
+ )
32
+ person_id = test_person["data"]["id"]
33
+ puts " Created test person: Jane Smith (#{person_id})"
34
+
35
+ # Working with Notes
36
+ puts "\n📝 Creating Notes:"
37
+
38
+ # Create a meeting note
39
+ meeting_note = client.notes.create(
40
+ parent_object: "people",
41
+ parent_record_id: person_id,
42
+ title: "Initial Meeting - Product Requirements",
43
+ content: <<~MARKDOWN
44
+ ## Meeting Summary
45
+ **Date:** #{Date.today}
46
+ **Attendees:** Jane Smith, Development Team
47
+
48
+ ### Discussion Points:
49
+ 1. **Q1 Product Roadmap**
50
+ - Feature A: User authentication improvements
51
+ - Feature B: New dashboard design
52
+ - Feature C: API v2 development
53
+
54
+ 2. **Timeline**
55
+ - Sprint 1: Jan 15-29
56
+ - Sprint 2: Jan 30-Feb 12
57
+ - Release: Feb 15
58
+
59
+ 3. **Action Items**
60
+ - [ ] Create detailed specs for Feature A
61
+ - [ ] Schedule design review for Feature B
62
+ - [ ] Allocate resources for API development
63
+
64
+ ### Next Steps:
65
+ Follow-up meeting scheduled for next week.
66
+ MARKDOWN
67
+ )
68
+ puts " ✅ Created meeting note: #{meeting_note['data']['title']}"
69
+
70
+ # Create a follow-up note
71
+ followup_note = client.notes.create(
72
+ parent_object: "people",
73
+ parent_record_id: person_id,
74
+ title: "Follow-up: Action Items",
75
+ content: "Confirmed timeline and resource allocation. Jane will provide detailed specs by EOW."
76
+ )
77
+ puts " ✅ Created follow-up note"
78
+
79
+ # List all notes for the person
80
+ notes = client.notes.list(
81
+ parent_object: "people",
82
+ parent_record_id: person_id
83
+ )
84
+ puts "\n 📋 Notes for Jane Smith:"
85
+ notes["data"].each do |note|
86
+ puts " - #{note['title']} (created: #{note['created_at']})"
87
+ end
88
+
89
+ # Working with Tasks
90
+ puts "\n✅ Creating Tasks:"
91
+
92
+ # Create tasks based on action items
93
+ task1 = client.tasks.create(
94
+ parent_object: "people",
95
+ parent_record_id: person_id,
96
+ title: "Create detailed specs for Feature A",
97
+ description: "Write comprehensive specifications for the user authentication improvements",
98
+ due_date: (Date.today + 7).iso8601,
99
+ priority: 1,
100
+ status: "pending"
101
+ )
102
+ puts " ✅ Created task: #{task1['data']['title']}"
103
+
104
+ task2 = client.tasks.create(
105
+ parent_object: "people",
106
+ parent_record_id: person_id,
107
+ title: "Schedule design review for Feature B",
108
+ description: "Coordinate with design team and schedule review meeting",
109
+ due_date: (Date.today + 3).iso8601,
110
+ priority: 2,
111
+ status: "pending"
112
+ )
113
+ puts " ✅ Created task: #{task2['data']['title']}"
114
+
115
+ task3 = client.tasks.create(
116
+ parent_object: "people",
117
+ parent_record_id: person_id,
118
+ title: "Allocate resources for API development",
119
+ description: "Determine team members and timeline for API v2",
120
+ due_date: (Date.today + 10).iso8601,
121
+ priority: 3,
122
+ status: "pending"
123
+ )
124
+ puts " ✅ Created task: #{task3['data']['title']}"
125
+
126
+ # List pending tasks
127
+ puts "\n 📋 Pending Tasks:"
128
+ pending_tasks = client.tasks.list(
129
+ status: "pending",
130
+ parent_object: "people",
131
+ parent_record_id: person_id
132
+ )
133
+ pending_tasks["data"].each do |task|
134
+ puts " - #{task['title']} (due: #{task['due_date']})"
135
+ end
136
+
137
+ # Complete a task
138
+ puts "\n ✅ Completing task..."
139
+ completed_task = client.tasks.complete(
140
+ id: task2["data"]["id"],
141
+ completed_at: DateTime.now.iso8601
142
+ )
143
+ puts " Task completed: #{completed_task['data']['title']}"
144
+
145
+ # Update a task
146
+ puts "\n 📝 Updating task..."
147
+ updated_task = client.tasks.update(
148
+ id: task1["data"]["id"],
149
+ description: "Updated: Include security requirements in the specifications",
150
+ priority: 1
151
+ )
152
+ puts " Task updated: #{updated_task['data']['title']}"
153
+
154
+ # Get task details
155
+ puts "\n 🔍 Task Details:"
156
+ task_detail = client.tasks.get(id: task1["data"]["id"])
157
+ puts " Title: #{task_detail['data']['title']}"
158
+ puts " Description: #{task_detail['data']['description']}"
159
+ puts " Due Date: #{task_detail['data']['due_date']}"
160
+ puts " Status: #{task_detail['data']['status']}"
161
+
162
+ # Update a note
163
+ puts "\n 📝 Updating note..."
164
+ client.notes.update(
165
+ id: followup_note["data"]["id"],
166
+ content: "Updated: Specs delivered. Design review scheduled for Friday."
167
+ )
168
+ puts " Note updated successfully"
169
+
170
+ # Cleanup
171
+ puts "\n🧹 Cleaning up..."
172
+
173
+ # Delete tasks
174
+ [task1, task2, task3].each do |task|
175
+ client.tasks.delete(id: task["data"]["id"])
176
+ end
177
+ puts " ✅ Deleted tasks"
178
+
179
+ # Delete notes
180
+ [meeting_note, followup_note].each do |note|
181
+ client.notes.delete(id: note["data"]["id"])
182
+ end
183
+ puts " ✅ Deleted notes"
184
+
185
+ # Delete test person
186
+ client.records.delete(object: "people", id: person_id)
187
+ puts " ✅ Deleted test person"
188
+ rescue Attio::Error => e
189
+ puts "❌ Error: #{e.message}"
190
+ # Cleanup on error
191
+ if defined?(person_id)
192
+ begin
193
+ client.records.delete(object: "people", id: person_id)
194
+ rescue StandardError
195
+ nil
196
+ end
197
+ end
198
+ end
199
+
200
+ puts "\n✨ Example completed!"
data/lib/attio/client.rb CHANGED
@@ -116,5 +116,41 @@ module Attio
116
116
  def users
117
117
  @users ||= Resources::Users.new(self)
118
118
  end
119
+
120
+ # Access to the Notes API resource.
121
+ #
122
+ # @return [Resources::Notes] Notes resource instance
123
+ # @example
124
+ # notes = client.notes.list(parent_object: 'people', parent_record_id: 'rec_123')
125
+ def notes
126
+ @notes ||= Resources::Notes.new(self)
127
+ end
128
+
129
+ # Access to the Tasks API resource.
130
+ #
131
+ # @return [Resources::Tasks] Tasks resource instance
132
+ # @example
133
+ # tasks = client.tasks.list(status: 'pending')
134
+ def tasks
135
+ @tasks ||= Resources::Tasks.new(self)
136
+ end
137
+
138
+ # Access to the Comments API resource.
139
+ #
140
+ # @return [Resources::Comments] Comments resource instance
141
+ # @example
142
+ # comments = client.comments.list(thread_id: 'thread_123')
143
+ def comments
144
+ @comments ||= Resources::Comments.new(self)
145
+ end
146
+
147
+ # Access to the Threads API resource.
148
+ #
149
+ # @return [Resources::Threads] Threads resource instance
150
+ # @example
151
+ # threads = client.threads.list(parent_object: 'companies', parent_record_id: 'rec_456')
152
+ def threads
153
+ @threads ||= Resources::Threads.new(self)
154
+ end
119
155
  end
120
156
  end
@@ -21,8 +21,12 @@ module Attio
21
21
  @timeout = timeout
22
22
  end
23
23
 
24
- def get(path, params = {})
25
- execute_request(:get, path, params: params)
24
+ def get(path, params = nil)
25
+ if params && !params.empty?
26
+ execute_request(:get, path, params: params)
27
+ else
28
+ execute_request(:get, path)
29
+ end
26
30
  end
27
31
 
28
32
  def post(path, body = {})
@@ -37,8 +41,12 @@ module Attio
37
41
  execute_request(:put, path, body: body.to_json)
38
42
  end
39
43
 
40
- def delete(path)
41
- execute_request(:delete, path)
44
+ def delete(path, params = nil)
45
+ if params
46
+ execute_request(:delete, path, body: params.to_json)
47
+ else
48
+ execute_request(:delete, path)
49
+ end
42
50
  end
43
51
 
44
52
  private def execute_request(method, path, options = {})
@@ -24,22 +24,71 @@ module Attio
24
24
  @client = client
25
25
  end
26
26
 
27
+ # Common validation methods that can be used by all resource classes
28
+
29
+ # Validates that an ID parameter is present and not empty
30
+ # @param id [String] The ID to validate
31
+ # @param resource_name [String] The resource name for the error message
32
+ # @raise [ArgumentError] if id is nil or empty
33
+ private def validate_id!(id, resource_name = "Resource")
34
+ return unless id.nil? || id.to_s.strip.empty?
35
+
36
+ raise ArgumentError, "#{resource_name} ID is required"
37
+ end
38
+
39
+ # Validates that data is not empty
40
+ # @param data [Hash] The data to validate
41
+ # @param operation [String] The operation name for the error message
42
+ # @raise [ArgumentError] if data is empty
43
+ private def validate_data!(data, operation = "Operation")
44
+ raise ArgumentError, "#{operation} data is required" if data.nil? || data.empty?
45
+ end
46
+
47
+ # Validates that a string parameter is present and not empty
48
+ # @param value [String] The value to validate
49
+ # @param field_name [String] The field name for the error message
50
+ # @raise [ArgumentError] if value is nil or empty
51
+ private def validate_required_string!(value, field_name)
52
+ return unless value.nil? || value.to_s.strip.empty?
53
+
54
+ raise ArgumentError, "#{field_name} is required"
55
+ end
56
+
57
+ # Validates parent object and record ID together
58
+ # @param parent_object [String] The parent object type
59
+ # @param parent_record_id [String] The parent record ID
60
+ # @raise [ArgumentError] if either is missing
61
+ private def validate_parent!(parent_object, parent_record_id)
62
+ validate_required_string!(parent_object, "Parent object")
63
+ validate_required_string!(parent_record_id, "Parent record ID")
64
+ end
65
+
27
66
  private def request(method, path, params = {})
67
+ connection = client.connection
68
+
28
69
  case method
29
70
  when :get
30
- client.connection.get(path, params)
71
+ handle_get_request(connection, path, params)
31
72
  when :post
32
- client.connection.post(path, params)
73
+ connection.post(path, params)
33
74
  when :patch
34
- client.connection.patch(path, params)
75
+ connection.patch(path, params)
35
76
  when :put
36
- client.connection.put(path, params)
77
+ connection.put(path, params)
37
78
  when :delete
38
- client.connection.delete(path)
79
+ handle_delete_request(connection, path, params)
39
80
  else
40
81
  raise ArgumentError, "Unsupported HTTP method: #{method}"
41
82
  end
42
83
  end
84
+
85
+ private def handle_get_request(connection, path, params)
86
+ params.empty? ? connection.get(path) : connection.get(path, params)
87
+ end
88
+
89
+ private def handle_delete_request(connection, path, params)
90
+ params.empty? ? connection.delete(path) : connection.delete(path, params)
91
+ end
43
92
  end
44
93
  end
45
94
  end