Connectors
/
Attio
Live · 50 tools

Attio Integration for AI Agents

The Attio API is clean and well-documented. Getting your agent to call it correctly — for real users, across workspaces, with fine-grained scope control and token isolation — is the part that takes longer than it should.
Attio
Live

CRM

Sales

Status
Live
Tools
50 pre-built
Auth
OAuth 2.0
Credential storage
Sandbox support

OAuth 2.0

Auto token refresh

Revocation detection

BYOC required

The real problem

Why this is harder than it looks

Attio's REST API is well-designed and the documentation is thorough. Most developers expect to spend an afternoon wiring up a connection. The complexity arrives when you move from a single-workspace prototype to a multi-tenant product with real users.

The first thing to understand is that Attio requires you to register your own OAuth app at build.attio.com — there is no shared managed app you can borrow for getting started. You supply your Client ID and Client Secret, configure the redirect URI exactly as registered, and own the full token lifecycle from day one. That means per-user token storage that is securely isolated across tenants, proactive refresh logic before access tokens expire, and revocation detection when a user disconnects your app from their Attio workspace settings.

Then there is Attio's scope model. Attio exposes a fine-grained set of OAuth scopes — separate scopes for reading vs. writing records, notes, tasks, comments, lists, and workspace configuration. Requesting too broad a scope triggers friction on the consent screen for security-conscious teams; requesting too narrow a scope causes silent API failures at runtime when your agent tries to write a note or create a task. Getting scope selection right requires understanding Attio's data model upfront, and any capability expansion later requires re-authorization of existing users.

Finally, Attio's data model is meaningfully different from conventional CRMs. Attribute values are typed arrays — even single-value fields like a company name must be passed as an array. Filter syntax varies per attribute type, and UUIDs are used everywhere instead of slugs. An agent that hardcodes field names or assumes flat record structures will fail silently across different customer workspaces. Scalekit handles the auth plumbing — token storage, refresh, revocation detection, per-user isolation — so your agent code can focus on the data model, not the auth model.

Capabilities

What your agent can do with Attio

Once connected, your agent has 50 pre-built tools covering Attio's full CRM object model:

  • Manage people, companies, and deals: create, retrieve, list, filter, and delete records across all standard CRM objects
  • Work with tasks and notes: create tasks with deadlines and assignees, add notes in plaintext or Markdown, link both to any CRM record
  • Search and filter records: fuzzy text search across objects or precise attribute-based filtering with full sort and pagination support
  • Manage lists and entries: add or remove records from Attio lists, retrieve list-level attribute values, track pipeline stage per entry
  • Inspect workspace schema: discover objects, attributes, select options, and status values at runtime — essential for agents working across diverse customer workspaces
Setup context

What we're building

This guide connects a CRM assistant agent to Attio — helping sales and operations teams manage contacts, companies, deals, tasks, and notes without leaving your product.

🤖
Example agent
CRM assistant creating records, logging notes, and managing tasks on behalf of each sales rep
🔐
Auth model
B2B SaaS — each rep connects their own Attio workspace. identifier = your user ID
⚙️
Scalekit account
app.scalekit.com — Client ID, Secret, Env URL
🔑
Attio OAuth app
Register at build.attio.com — required for BYOC setup
Setup

1 Setup: One SDK, One credential

Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Attio secrets, no user tokens, nothing belonging to your customers.

pip install scalekit-sdk-python
import scalekit.client import os from dotenv import load_dotenv load_dotenv() scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) actions = scalekit.actions
import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET ); const actions = scalekit.actions;
Already have credentials?
Connected Accounts

2 Per-User Auth: Creating connected accounts

Each rep gets their own Connected Account, giving them a dedicated auth context. The identifier is any unique string from your system — a UUID, email, whatever you use internally.

response = actions.get_or_create_connected_account( connection_name="attio", identifier="user_attio_123" # your internal user ID ) connected_account = response.connected_account print(f"Status: {connected_account.status}") # Status: PENDING — user hasn't authorized yet
const response = await actions.getOrCreateConnectedAccount({ connectionName: "attio", identifier: "user_attio_123" // your internal user ID }); const connectedAccount = response.connectedAccount; console.log(`Status: ${connectedAccount.status}`); // Status: PENDING — user hasn't authorized yet

This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.

Authorization Flow

3 The authorization flow

The rep authorizes your agent once. Scalekit generates the OAuth URL with correct scopes, PKCE challenge, and redirect handling pre-configured. After approval, you never see the token.

if connected_account.status != "ACTIVE": link = actions.get_authorization_link( connection_name="attio", identifier="user_attio_123" ) # Redirect user → Attio OAuth consent screen # Scalekit captures the token on callback return redirect(link.link)
if (connectedAccount.status !== "ACTIVE") { const { link } = await actions.getAuthorizationLink({ connectionName: "attio", identifier: "user_attio_123" }); // Redirect user → Attio OAuth consent screen // Scalekit captures the token on callback return redirect(link); }
Token management is automatic
After the user approves, Scalekit stores encrypted tokens and the connected account moves to ACTIVE. Access tokens refresh before expiry. If a rep revokes access from their Attio workspace settings, the account moves to REVOKED — no silent failures. Check account.status before critical operations.
BYOC is required for Attio
Attio does not provide a managed shared app. You must register your own OAuth app at build.attio.com, copy the Scalekit redirect URI into your app's OAuth settings, configure the required scopes, then paste your Client ID and Client Secret into the Scalekit dashboard. Token management is fully handled after that one-time setup.
Calling Attio

4 Calling Attio: What your agent writes

With the connected account active, your agent calls Attio actions using execute_tool. Name the tool, pass parameters. Scalekit handles token retrieval, request construction, and response parsing.

Create a company

Attio attribute values are typed arrays — even single-value fields like name must be passed as an array. Use attio_list_attributes to discover the expected structure for any object before writing.

result = actions.execute_tool( identifier="user_attio_123", tool_name="attio_create_company", tool_input={ "values": { "name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}], "description": [{"value": "Enterprise SaaS company"}] } } ) # Returns: { "id": { "record_id": "..." }, "values": { ... } }
const result = await actions.executeTool({ identifier: "user_attio_123", toolName: "attio_create_company", toolInput: { values: { name: [{ value: "Acme Corp" }], domains: [{ domain: "acme.com" }], description: [{ value: "Enterprise SaaS company" }] } } }); // Returns: { id: { record_id: "..." }, values: { ... } }

Create a person and link to a company

Pass the company's record_id from the previous step to associate the person. Multi-value attributes like email_addresses require the full typed structure.

result = actions.execute_tool( identifier="user_attio_123", tool_name="attio_create_person", tool_input={ "values": { "name": [{"first_name": "Jane", "last_name": "Smith"}], "email_addresses": [{ "email_address": "jane@acme.com", "attribute_type": "email" }], "company": [{"target_record_id": ""}] } } )
const result = await actions.executeTool({ identifier: "user_attio_123", toolName: "attio_create_person", toolInput: { values: { name: [{ first_name: "Jane", last_name: "Smith" }], email_addresses: [{ email_address: "jane@acme.com", attribute_type: "email" }], company: [{ target_record_id: "" }] } } });

List and filter records

Use attio_list_records for precise attribute-based filtering. Filter syntax varies per attribute type — use attio_list_attributes first to confirm slugs and expected value structure.

result = actions.execute_tool( identifier="user_attio_123", tool_name="attio_list_records", tool_input={ "object": "people", "filter": { "email_addresses": [{ "email_address": {"$eq": "jane@acme.com"} }] }, "limit": 10 } )
const result = await actions.executeTool({ identifier: "user_attio_123", toolName: "attio_list_records", toolInput: { object: "people", filter: { email_addresses: [{ email_address: { "$eq": "jane@acme.com" } }] }, limit: 10 } });

Create a task linked to a record

Tasks can be linked to any CRM record and assigned to workspace members. Use attio_list_workspace_members to resolve member UUIDs before assigning.

from datetime import datetime, timedelta, timezone deadline = (datetime.now(timezone.utc) + timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%S.000Z") result = actions.execute_tool( identifier="user_attio_123", tool_name="attio_create_task", tool_input={ "content": "Send Q2 pricing proposal to Jane", "deadline_at": deadline, "is_completed": False, "linked_records": [{ "target_object": "people", "target_record_id": "" }] } ) # Returns: { "id": { "task_id": "..." }, "content": "...", ... }
const deadline = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); const result = await actions.executeTool({ identifier: "user_attio_123", toolName: "attio_create_task", toolInput: { content: "Send Q2 pricing proposal to Jane", deadline_at: deadline, is_completed: false, linked_records: [{ target_object: "people", target_record_id: "" }] } }); // Returns: { id: { task_id: "..." }, content: "...", ... }
Framework wiring

5 Wiring into your agent framework

Scalekit integrates directly with LangChain. The agent decides what to call; Scalekit handles auth on every invocation. No token plumbing in your agent logic.

from langchain_anthropic import ChatAnthropic from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from scalekit.langchain import get_tools attio_tools = get_tools( connection_name="attio", identifier="user_attio_123" ) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a CRM assistant. Use the available tools to help manage Attio contacts, companies, deals, tasks, and notes."), MessagesPlaceholder("chat_history", optional=True), ("human", "{input}"), MessagesPlaceholder("agent_scratchpad"), ]) agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), attio_tools, prompt) result = AgentExecutor(agent=agent, tools=attio_tools).invoke({ "input": "Find Jane Smith's contact record, log a note about today's call, and create a follow-up task for next week" })
import { ChatAnthropic } from "@langchain/anthropic"; import { AgentExecutor, createToolCallingAgent } from "langchain/agents"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; import { getTools } from "@scalekit-sdk/langchain"; const attioTools = getTools({ connectionName: "attio", identifier: "user_attio_123" }); const prompt = ChatPromptTemplate.fromMessages([ ["system", "You are a CRM assistant. Use the available tools to help manage Attio contacts, companies, deals, tasks, and notes."], new MessagesPlaceholder("chat_history", true), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), ]); const agent = await createToolCallingAgent({ llm: new ChatAnthropic({ model: "claude-sonnet-4-6" }), tools: attioTools, prompt }); const result = await AgentExecutor.fromAgentAndTools({ agent, tools: attioTools }).invoke({ input: "Find Jane Smith's contact record, log a note about today's call, and create a follow-up task for next week" });
Other frameworks supported
Tool reference

All 50 Attio tools

Grouped by object and capability. Your agent calls tools by name — no API wrappers to write.

People
attio_create_person
Create a person record with name, email, phone, and company association. Throws on unique attribute conflicts
attio_list_people
List people with attribute-based filtering, sorting, and pagination
attio_get_person
Retrieve a single person record by UUID with full attribute and audit metadata
attio_delete_person
Permanently delete a person record by UUID. Irreversible
Companies
attio_create_company
Create a company with name, domain, description, and custom attributes. Throws if domain already exists
attio_list_companies
List companies with attribute-based filtering, sorting, and pagination
attio_get_company
Retrieve a single company by UUID with full attribute and audit metadata
attio_delete_company
Permanently delete a company record by UUID. Irreversible
Deals
attio_create_deal
Create a deal with name, value (currency), stage, and company association
attio_list_deals
List deals with attribute-based filtering, sorting, and pagination
attio_get_deal
Retrieve a single deal by UUID with full attribute and audit metadata
attio_delete_deal
Permanently delete a deal record by UUID. Irreversible
Generic Records
attio_create_record
Create a record for any object type — standard or custom. Use when the object type is determined at runtime
attio_list_records
List records for any object with precise attribute-based filtering. Prefer over search for exact matches
attio_search_records
Fuzzy text search across any object by name, email, or domain. Use for user-facing search flows
attio_get_record
Retrieve a record by object type and UUID with full audit trail per attribute value
attio_delete_record
Permanently delete any record by object type and UUID. Irreversible
attio_get_record_attribute_values
Retrieve all values (including history) for a specific attribute on a record
Tasks
attio_create_task
Create a task with content, deadline, assignees, and links to one or more CRM records
attio_list_tasks
List tasks filtered by linked record, completion status, with pagination
attio_get_task
Retrieve a single task by UUID with content, deadline, assignees, and linked records
attio_delete_task
Permanently delete a task by UUID. Irreversible
Notes
attio_create_note
Create a note on any record in plaintext or Markdown. Supports backdating and meeting association
attio_list_notes
List notes for a specific record with pagination (max 50 per page)
attio_get_note
Retrieve a note by UUID with title, content in both plaintext and Markdown, and creator info
attio_delete_note
Permanently delete a note by UUID. Irreversible
Comments & Threads
attio_create_comment
Create a comment on a record or list entry. Supports threading via thread_id
attio_get_comment
Retrieve a comment by UUID with content, author, thread ID, and resolution status
attio_delete_comment
Delete a comment. Deleting a thread head also removes all replies
attio_list_threads
List all comment threads on a record or list entry with messages and resolution status
Lists & Entries
attio_list_lists
Retrieve all CRM lists in the workspace — pipelines, outreach targets, and custom groupings
attio_create_list
Create a new CRM list with name, slug, and workspace access configuration
attio_get_list
Retrieve list details by UUID or slug including access configuration
attio_list_entries
List entries in a CRM list with filtering, sorting, and pagination. Returns list-level attribute values per entry
attio_get_list_entry
Retrieve a single list entry by entry UUID with list-specific attribute values
attio_add_to_list
Add a record to a list. Optionally set list-level attributes (e.g., pipeline stage) on the entry
attio_remove_from_list
Remove a specific entry from a list by entry UUID. Does not delete the underlying record
attio_list_record_entries
List all list memberships for a record across all lists — returns list IDs and entry IDs for removal
Schema & Objects
attio_list_objects
List all objects in the workspace — system objects and custom objects. Call first to discover available types
attio_get_object
Retrieve details of a single object by slug or UUID
attio_create_object
Create a new custom object type with singular/plural names and API slug
attio_list_attributes
List attribute schema for any object or list — slugs, types, and config. Call before filtering or writing
attio_get_attribute
Retrieve full attribute details including type, config, required/unique flags, and metadata
attio_create_attribute
Add a new attribute to any object or list. Supports 15 types including text, select, status, currency, and record-reference
attio_list_attribute_options
List valid options for a select or multiselect attribute before writing
attio_list_attribute_statuses
List valid statuses for a status attribute before writing
Workspace & Members
attio_list_workspace_members
List workspace members to resolve UUIDs for task assignment and actor-reference attributes
attio_get_workspace_member
Retrieve a member by UUID with name, email, and access level
attio_list_user_records
List end-user records (distinct from workspace members) with filtering and pagination
attio_delete_user_record
Permanently delete a user record by UUID
attio_list_workspace_records
List workspace records (connected SaaS product instances) with filtering and pagination
attio_get_workspace_record
Retrieve a workspace record by UUID with full attribute and audit metadata
attio_delete_workspace_record
Permanently delete a workspace record by UUID
Webhooks, Meetings & Token
attio_list_webhooks
Retrieve all webhooks with event subscriptions and statuses
attio_get_webhook
Retrieve a single webhook by UUID with target URL and subscribed event types
attio_delete_webhook
Permanently delete a webhook by UUID
attio_list_meetings
List meetings in the workspace with optional participant and record filters (beta)
attio_get_current_token_info
Verify the active token, connected workspace, authenticated actor, and granted OAuth scopes
Connector notes

Attio-specific behavior

Attribute values are always arrays
Attio's data model requires attribute values to be typed arrays — even single-value fields like company name must be passed as an array with one item. Multi-value attributes like email_addresses follow the same pattern. The structure varies per attribute type. Always call attio_list_attributes on the target object before writing to confirm the correct value format.
IDs are UUIDs — resolve slugs at runtime
Attio uses UUIDs for records, attributes, workspace members, lists, and entries — not human-readable slugs. Use attio_list_objects, attio_list_attributes, and attio_list_workspace_members to resolve the UUIDs your agent needs before creating or updating records. An agent that hardcodes IDs from one workspace will break silently in another.
Verify scopes before write operations
Use attio_get_current_token_info before performing write operations to confirm the required scope is present. Attio enforces fine-grained scopes per resource type — a token with record_permission:read cannot create notes or tasks. A missing scope returns a clear API error, but checking proactively prevents mid-workflow failures.
Infrastructure decision

Why not build this yourself

The Attio OAuth flow is documented. Token storage isn't technically hard. But here's what you're actually signing up for:

PROBLEM 01
BYOC with no managed app fallback — you register and maintain your own Attio OAuth app, including redirect URI configuration and credential rotation
PROBLEM 02
Fine-grained scope selection across 12+ resource-level scopes — under-scoping causes runtime failures, over-scoping causes enterprise security teams to reject the consent screen
PROBLEM 03
Per-user token isolation across a multi-tenant system — one rep's Attio workspace credentials must never be accessible to another
PROBLEM 04
Revocation detection when users disconnect your app from Attio — and proactive refresh before token expiry so agents don't fail mid-operation

That's one connector. Your agent product will eventually need Salesforce, HubSpot, Gmail, Linear, and whatever else your customers ask for. Each has its own OAuth quirks and failure modes.

Scalekit maintains every connector. You maintain none of them.

Ready to ship

Connect your agent to Attio now

Free to start. Token management fully handled.