Attio Ruby SDK
A Ruby SDK for the Attio API. This gem provides a simple and intuitive interface for interacting with Attio's CRM platform.
Table of Contents
- Installation
- Quick Start
- Configuration
- Authentication
- Basic Usage
- Working with Objects
- Managing Records
- Lists and List Entries
- Notes
- Webhooks
- Advanced Features
- OAuth 2.0
- Error Handling
- Examples
- Testing
- Performance
- Contributing
- License
Installation
Add this line to your application's Gemfile:
gem 'attio-ruby'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install attio-ruby
Quick Start
require 'attio'
# Configure the client
Attio.configure do |config|
config.api_key = ENV['ATTIO_API_KEY']
end
# Create a person
person = Attio::Person.create(
first_name: "John",
last_name: "Doe",
email: "john@example.com"
)
# Search for companies
companies = Attio::Company.search("tech")
Configuration
The gem can be configured globally or on a per-request basis:
Global Configuration
Attio.configure do |config|
# Required
config.api_key = "your_api_key"
# Optional
config.api_base = "https://api.attio.com" # Default
config.api_version = "v2" # Default
config.timeout = 30 # Request timeout in seconds
config.max_retries = 3 # Number of retries for failed requests
config.debug = false # Enable debug logging
config.logger = Logger.new(STDOUT) # Custom logger
end
Environment Variables
The gem automatically reads configuration from environment variables:
-
ATTIO_API_KEY
- Your API key -
ATTIO_API_BASE
- API base URL (optional) -
ATTIO_DEBUG
- Enable debug mode (optional)
Per-Request Configuration
# Override configuration for a single request
person = Attio::Person.create(
first_name: "Jane",
last_name: "Doe",
api_key: "different_api_key"
)
Authentication
API Key Authentication
The simplest way to authenticate is using an API key:
Attio.configure do |config|
config.api_key = "your_api_key"
end
OAuth 2.0 Authentication
For user-facing applications, use OAuth 2.0. The gem includes OAuth support, but for a complete OAuth integration example, see our companion Rails application (coming soon).
# Initialize OAuth client
oauth_client = Attio::OAuth::Client.new(
client_id: ENV['ATTIO_CLIENT_ID'],
client_secret: ENV['ATTIO_CLIENT_SECRET'],
redirect_uri: "https://yourapp.com/callback"
)
# Generate authorization URL
auth_data = oauth_client.authorization_url(
scopes: %w[record:read record:write],
state: "random_state"
)
redirect_to auth_data[:url]
# Exchange code for token
token = oauth_client.exchange_code_for_token(code: params[:code])
# Use the token
Attio.configure do |config|
config.api_key = token.access_token
end
Basic Usage
Working with Objects
Objects represent the different types of records in your workspace (e.g., People, Companies).
# List all objects
objects = Attio::Object.list
objects.each do |object|
puts "#{object.plural_noun} (#{object.api_slug})"
end
# Get a specific object
people_object = Attio::Object.retrieve("people")
puts people_object.name # => "People"
Managing Records
Records are instances of objects (e.g., individual people or companies). The gem provides typed classes (Attio::Person
, Attio::Company
) that inherit from TypedRecord
, offering a cleaner interface than the generic Attio::Record
class.
Complex Attributes
The gem provides convenient methods for working with complex attributes. You can use the simplified interface or the raw API format:
Simple Interface (Recommended):
# The gem handles the complex structure for you
person = Attio::Person.create(
first_name: "John",
last_name: "Smith",
email: "john@example.com",
phone: "+15558675309",
job_title: "Developer"
)
company = Attio::Company.create(
name: "Acme Corp",
domain: "acme.com",
employee_count: "50-100"
)
Raw API Format (Advanced): If you need full control, you can use the raw API structures:
# Names
values: {
name: [{
first_name: "John",
last_name: "Smith",
full_name: "John Smith"
}]
}
# Phone Numbers
values: {
phone_numbers: [{
original_phone_number: "+15558675309",
country_code: "US"
}]
}
# Addresses
values: {
primary_location: [{
line_1: "1 Infinite Loop",
locality: "Cupertino",
region: "CA",
postcode: "95014",
country_code: "US"
}]
}
# Email addresses and domains (simple arrays)
values: {
email_addresses: ["john@example.com", "john.smith@company.com"],
domains: ["example.com", "example.org"]
}
Creating Records
# Create a person
person = Attio::Person.create(
first_name: "Jane",
last_name: "Smith",
email: "jane@example.com",
phone: "+1-555-0123",
job_title: "CEO"
)
# Create a company
company = Attio::Company.create(
name: "Acme Corp",
domain: "acme.com",
values: {
industry: "Technology"
}
)
Retrieving Records
# Get a specific person
person = Attio::Person.retrieve("rec_456def789")
# Access attributes using bracket notation
puts person[:name]
puts person[:email_addresses]
puts person[:job_title]
# Note: Attributes can be accessed with bracket notation and symbols
Updating Records
# Update a record using attribute setters
person[:job_title] = "CTO"
person[:tags] = ["vip", "customer"]
person.save
# Or update directly
Attio::Person.update(
"rec_456def789",
values: { job_title: "CTO" }
)
Searching and Filtering
# Simple search
people = Attio::Person.search("john")
# Advanced filtering
executives = Attio::Person.list(
params: {
filter: {
job_title: { "$contains": "CEO" }
},
sort: [{ attribute: "name", direction: "asc" }],
limit: 20
}
)
# Pagination
page = people
while page.has_more?
page.each do |person|
puts person[:name]
end
page = page.next_page
end
# Auto-pagination
people.auto_paging_each do |person|
puts person[:name]
end
Deleting Records
# Delete a record
person.destroy
# Or delete by ID
Attio::Person.delete("rec_123abc456") # Replace with actual record ID
Note on Batch Operations
The Attio API does not currently support batch operations for creating, updating, or deleting multiple records in a single request. Each record must be processed individually. If you need to process many records, consider implementing rate limiting and error handling in your application.
Convenience Methods
The gem provides many convenience methods to make working with records easier:
Person Methods
person = Attio::Person.retrieve("rec_123")
# Access methods
person.email # Returns primary email address
person.phone # Returns primary phone number
person.first_name # Returns first name
person.last_name # Returns last name
person.full_name # Returns full name
# Modification methods
person.set_name(first: "Jane", last: "Doe")
person.add_email("jane.doe@example.com")
person.add_phone("+14155551234", country_code: "US")
# Search methods - Rails-style find_by
jane = Attio::Person.find_by(email: "jane@example.com")
john = Attio::Person.find_by(name: "John Smith")
# Can combine multiple conditions (uses AND logic)
exec = Attio::Person.find_by(email: "exec@company.com", job_title: "CEO")
Company Methods
company = Attio::Company.retrieve("rec_456")
# Access methods
company.name # Returns company name
company.domain # Returns primary domain
company.domains_list # Returns all domains
# Modification methods
company.name = "New Company Name"
company.add_domain("newdomain.com")
company.add_team_member(person) # Associate a person with the company
# Search methods - Rails-style find_by
acme = Attio::Company.find_by(name: "Acme Corp")
tech_co = Attio::Company.find_by(domain: "techcompany.com")
# Can combine multiple conditions
big_tech = Attio::Company.find_by(domain: "tech.com", employee_count: "100-500")
Deal Methods
# Create a deal (requires name, stage, and owner)
deal = Attio::Deal.create(
name: "Enterprise Deal",
value: 50000,
stage: "In Progress", # Options: "Lead", "In Progress", "Won 🎉", "Lost"
owner: "sales@company.com" # Must be a workspace member email
)
# Access methods
deal.name # Returns deal name
deal.value # Returns currency object with currency_value
deal.stage # Returns status object with nested title
deal.status # Alias for stage
deal.current_status # Returns the current status title as a string
deal.status_changed_at # Returns when the status was last changed
# Update methods
deal.update_stage("Won 🎉")
deal.update_value(75000)
# Search methods
big_deals = Attio::Deal.find_by_value_range(min: 100000)
mid_deals = Attio::Deal.find_by_value_range(min: 50000, max: 100000)
won_deals = Attio::Deal.find_by(stage: "Won 🎉")
# Query by status using convenience methods
won_deals = Attio::Deal.won # All deals with "Won 🎉" status
lost_deals = Attio::Deal.lost # All deals with "Lost" status
open_deals = Attio::Deal.open_deals # All deals with "Lead" or "In Progress"
# Query by custom stages
custom_deals = Attio::Deal.in_stage(stage_names: ["Won 🎉", "Contract Signed"])
# Check deal status (uses configuration)
deal.open? # true if status is "Lead" or "In Progress"
deal.won? # true if status is "Won 🎉"
deal.lost? # true if status is "Lost"
deal.won_at # timestamp when deal was won (or nil)
deal.closed_at # timestamp when deal was closed (won or lost)
# Associate with companies and people
deal = Attio::Deal.create(
name: "Partnership Deal",
value: 100000,
stage: "Lead",
owner: "sales@company.com",
associated_people: ["contact@partner.com"],
associated_company: ["partner.com"] # Uses domain
)
Customizing Deal Statuses
The gem uses Attio's default deal statuses ("Lead", "In Progress", "Won 🎉", "Lost") but you can customize these for your workspace:
# In config/initializers/attio.rb
Attio.configure do |config|
config.api_key = ENV["ATTIO_API_KEY"]
# Customize which statuses are considered won, lost, or open
config.won_statuses = ["Won 🎉", "Contract Signed", "Customer"]
config.lost_statuses = ["Lost", "Disqualified", "No Budget"]
config.open_statuses = ["Lead", "Qualified Lead", "Prospect"]
config.in_progress_statuses = ["In Progress", "Negotiation", "Proposal Sent"]
end
# Now the convenience methods use your custom statuses
won_deals = Attio::Deal.won # Finds deals with any of your won_statuses
deal.won? # Returns true if deal status is in your won_statuses
TypedRecord Methods
All typed records (Person, Company, and custom objects) support:
# Search with query string
results = Attio::Person.search("john")
# Find by any attribute using Rails-style syntax
person = Attio::Person.find_by(job_title: "CEO")
# Or find by multiple attributes (AND logic)
person = Attio::Person.find_by(job_title: "CEO", company: "Acme Corp")
# Aliases for common methods
Attio::Person.all == Attio::Person.list
Attio::Person.find("rec_123") == Attio::Person.retrieve("rec_123")
Lists and List Entries
Lists allow you to organize records into groups.
# Create a list
list = Attio::List.create(
name: "VIP Customers",
object: "people"
)
# Add records to a list
entry = list.add_record("rec_789def012") # Replace with actual record ID
# List entries
entries = list.entries
entries.each do |entry|
puts entry.record_id
end
# Remove from list (requires entry_id, not record_id)
list.remove_record("ent_456ghi789") # Replace with actual list entry ID
# Delete list
list.destroy
Notes
Add notes to records to track interactions and important information.
# Create a note
note = Attio::Note.create(
parent_object: "people",
parent_record_id: "rec_123abc456", # Replace with actual record ID
content: "Had a great meeting about the new project.",
format: "plaintext" # or "markdown"
)
# List notes for a record
notes = Attio::Note.list(
parent_object: "people",
parent_record_id: "rec_123abc456" # Replace with actual record ID
)
# Notes are immutable - create a new note instead of updating
# To "update" a note, you would delete the old one and create a new one
# Delete a note
note.destroy
Webhooks
Set up webhooks to receive real-time updates about changes in your workspace.
# Create a webhook
webhook = Attio::Webhook.create(
name: "Customer Updates",
url: "https://yourapp.com/webhooks/attio",
subscriptions: %w[record.created record.updated]
)
# List webhooks
webhooks = Attio::Webhook.list
# Update webhook
webhook[:active] = false
webhook.save
# Delete webhook
webhook.destroy
# Verify webhook signatures
Attio::Util::WebhookSignature.verify!(
payload: request.body.read,
signature: request.headers['Attio-Signature'],
secret: ENV['WEBHOOK_SECRET']
)
Advanced Features
OAuth 2.0
Complete OAuth 2.0 flow implementation:
# Initialize client
oauth = Attio::OAuth::Client.new(
client_id: ENV['CLIENT_ID'],
client_secret: ENV['CLIENT_SECRET'],
redirect_uri: "https://yourapp.com/callback"
)
# Authorization
auth_data = oauth.authorization_url(
scopes: %w[record:read record:write user:read],
state: SecureRandom.hex(16)
)
# Token exchange
token = oauth.exchange_code_for_token(
code: params[:code],
state: params[:state]
)
# Token refresh
new_token = oauth.refresh_token("rtok_xyz789ghi012") # Replace with actual refresh token
# Token introspection
info = oauth.introspect_token("tok_abc123def456") # Replace with actual access token
puts info[:active] # => true
# Token revocation
oauth.revoke_token("tok_abc123def456") # Replace with actual access token
Error Handling
The gem provides comprehensive error handling:
begin
person = Attio::Person.create(
email: "invalid-email"
)
rescue Attio::InvalidRequestError => e
puts "Validation error: #{e.message}"
puts "HTTP status: #{e.code}"
puts "Request ID: #{e.request_id}"
rescue Attio::AuthenticationError => e
puts "Auth failed: #{e.message}"
puts "Request ID: #{e.request_id}"
rescue Attio::RateLimitError => e
puts "Rate limited: #{e.message}"
rescue Attio::ConnectionError => e
puts "Network error: #{e.message}"
rescue Attio::Error => e
puts "API error: #{e.message}"
puts "HTTP status: #{e.code}"
puts "Request ID: #{e.request_id}"
end
Examples
Complete example applications are available in the examples/
directory:
-
basic_usage.rb
- Demonstrates core functionality -
oauth_flow.rb
- Complete OAuth 2.0 implementation with Sinatra -
webhook_server.rb
- Webhook handling with signature verification
Run an example:
$ ruby examples/basic_usage.rb
Testing
The gem includes comprehensive test coverage:
# Run all tests (unit tests only by default)
$ bundle exec rspec
# Run unit tests only
$ bundle exec rspec spec/unit
# Run integration tests (requires API key)
$ RUN_INTEGRATION_TESTS=true bundle exec rspec spec/integration
Integration Tests
Note: This gem is under active development. To ensure our implementation matches the Attio API, we leverage live integration tests against a sandbox environment. This strategy will be removed once we hit a stable 1.0 release.
Integration tests make real API calls to Attio and are disabled by default. They serve to:
- Validate that our WebMock stubs match actual API behavior
- Test OAuth flows and complex scenarios
- Ensure the gem works correctly with the latest Attio API
To run integration tests:
-
Set up your environment variables:
export ATTIO_API_KEY="your_api_key" export RUN_INTEGRATION_TESTS=true
-
Run the tests:
bundle exec rspec spec/integration
Warning: Integration tests will create and delete real data in your Attio workspace. They include automatic cleanup, but use a test workspace if possible.
Unit Tests
Unit tests use WebMock to stub all HTTP requests and do not require an API key. They run by default and ensure the gem's internal logic works correctly.
# Run only unit tests
bundle exec rspec spec/unit
# Run with coverage
$ COVERAGE=true bundle exec rspec
Performance
The gem is optimized for performance:
- Connection pooling for HTTP keep-alive
- Automatic retry with exponential backoff
- Efficient pagination with auto-paging
- Thread-safe operations
Run benchmarks:
$ ruby benchmarks/api_performance.rb
$ ruby benchmarks/memory_profile.rb
Contributing
We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
License
The gem is available as open source under the terms of the MIT License.