Skip to main content
Business & GrowthCrm Services200 lines

Attio

Integrate with Attio CRM API for managing objects, records, lists, notes,

Quick Summary25 lines
You are an expert in Attio CRM API integration using Attio's REST API v2. You build relationship-intelligence-driven CRM workflows that leverage Attio's flexible object model, automatic data enrichment, and real-time record management.

## Key Points

- **Hardcoding attribute slugs**: Attribute slugs can differ between Attio workspaces. Always fetch the object schema first to discover the correct slugs.
- **Ignoring the assert (PUT) endpoint**: Using POST to create records bypasses deduplication. Always use PUT with `matching_attribute` for upsert behavior.
- **Treating list entries as records**: List entries are separate from the underlying record. Updating a list entry's attributes does not modify the parent record's attributes.
- **Not paginating query results**: The query endpoint defaults to 25 results. Always implement offset-based pagination to retrieve complete result sets.
- Building relationship-intelligence workflows that track interactions across people and companies
- Managing custom sales pipelines using Attio lists as deal stages
- Syncing enriched company and contact data from Attio into external systems
- Automating note creation and activity logging from third-party communication tools
- Creating custom CRM objects and attributes programmatically for new business workflows

## Quick Example

```bash
npm install axios
```

```
ATTIO_API_KEY=attio_key_xxxxxxxxxxxxxxxx
```
skilldb get crm-services-skills/AttioFull skill: 200 lines
Paste into your CLAUDE.md or agent config

Attio CRM Integration

You are an expert in Attio CRM API integration using Attio's REST API v2. You build relationship-intelligence-driven CRM workflows that leverage Attio's flexible object model, automatic data enrichment, and real-time record management.

Core Philosophy

Object-Attribute Flexibility

Attio uses a configurable object model where standard objects (people, companies) and custom objects each have user-defined attributes. Never hardcode attribute slugs. Always query the object schema first to discover available attributes and their types.

Record Deduplication by Domain

Attio automatically deduplicates company records by domain and people by email. Leverage this by always providing email addresses and domains in write operations. The API will merge matching records rather than creating duplicates.

List-Driven Workflows

Attio lists are the primary workflow container, similar to pipeline views. Each list has entries that reference records. Use list entries (not raw records) for pipeline management, deal tracking, and process automation.

Setup

npm install axios
ATTIO_API_KEY=attio_key_xxxxxxxxxxxxxxxx

Initialize the client:

import axios, { AxiosInstance } from "axios";

const attio: AxiosInstance = axios.create({
  baseURL: "https://api.attio.com/v2",
  headers: {
    Authorization: `Bearer ${process.env.ATTIO_API_KEY}`,
    "Content-Type": "application/json",
  },
});

Key Patterns

Do: Assert records using matching attributes for upsert behavior

async function assertCompany(domain: string, name: string) {
  const { data } = await attio.put("/objects/companies/records", {
    data: {
      values: {
        domains: [{ domain }],
        name: [{ value: name }],
      },
    },
    matching_attribute: "domains",
  });
  return data.data;
}

Not: Creating records without matching attributes

// WRONG - creates duplicates instead of upserting
await attio.post("/objects/companies/records", {
  data: { values: { name: [{ value: "Acme" }] } },
});

Do: Query records using the filter API

async function findCompaniesByIndustry(industry: string) {
  const { data } = await attio.post("/objects/companies/records/query", {
    filter: {
      "categories.title": { "$eq": industry },
    },
    limit: 50,
    offset: 0,
  });
  return data.data;
}

Common Patterns

Create a Person Record

async function assertPerson(
  email: string,
  firstName: string,
  lastName: string
) {
  const { data } = await attio.put("/objects/people/records", {
    data: {
      values: {
        email_addresses: [{ email_address: email }],
        first_name: [{ first_name: firstName }],
        last_name: [{ last_name: lastName }],
      },
    },
    matching_attribute: "email_addresses",
  });
  return data.data;
}

Add a Record to a List (Pipeline Entry)

async function addListEntry(
  listSlug: string,
  recordId: string,
  objectSlug: string,
  entryAttributes?: Record<string, unknown>
) {
  const { data } = await attio.post(`/lists/${listSlug}/entries`, {
    data: {
      parent_record: { object: objectSlug, record_id: recordId },
      entry_values: entryAttributes || {},
    },
  });
  return data.data;
}

Query List Entries with Filters

async function queryListEntries(
  listSlug: string,
  filter?: Record<string, unknown>
) {
  const entries: any[] = [];
  let offset = 0;
  let hasMore = true;
  while (hasMore) {
    const { data } = await attio.post(`/lists/${listSlug}/entries/query`, {
      filter: filter || {},
      limit: 50,
      offset,
    });
    entries.push(...data.data);
    hasMore = data.data.length === 50;
    offset += 50;
  }
  return entries;
}

Create a Note on a Record

async function createNote(
  parentObject: string,
  parentRecordId: string,
  title: string,
  content: string
) {
  const { data } = await attio.post("/notes", {
    data: {
      parent_object: parentObject,
      parent_record_id: parentRecordId,
      title,
      format: "plaintext",
      content,
    },
  });
  return data.data;
}

Fetch Object Schema

async function getObjectAttributes(objectSlug: string) {
  const { data } = await attio.get(`/objects/${objectSlug}/attributes`);
  return data.data.map((attr: any) => ({
    slug: attr.api_slug,
    title: attr.title,
    type: attr.type,
    isRequired: attr.is_required,
  }));
}

Anti-Patterns

  • Hardcoding attribute slugs: Attribute slugs can differ between Attio workspaces. Always fetch the object schema first to discover the correct slugs.
  • Ignoring the assert (PUT) endpoint: Using POST to create records bypasses deduplication. Always use PUT with matching_attribute for upsert behavior.
  • Treating list entries as records: List entries are separate from the underlying record. Updating a list entry's attributes does not modify the parent record's attributes.
  • Not paginating query results: The query endpoint defaults to 25 results. Always implement offset-based pagination to retrieve complete result sets.

When to Use

  • Building relationship-intelligence workflows that track interactions across people and companies
  • Managing custom sales pipelines using Attio lists as deal stages
  • Syncing enriched company and contact data from Attio into external systems
  • Automating note creation and activity logging from third-party communication tools
  • Creating custom CRM objects and attributes programmatically for new business workflows

Install this skill directly: skilldb add crm-services-skills

Get CLI access →