# Attio CRM TypeScript SDK The Attio TypeScript SDK is a modern, type-safe client library for interacting with the Attio CRM API. It provides full coverage of the Attio API including people, companies, lists, notes, tasks, meetings, and webhooks, with runtime validation using Zod v4 schemas. The SDK is built on an OpenAPI-generated foundation with an additional convenience layer that adds automatic retries, error normalization, metadata caching, and pagination helpers. The SDK offers two usage patterns: a high-level Attio helpers layer (recommended) with functions like `createRecord`, `queryRecords`, and `searchRecords`, and direct access to the generated OpenAPI endpoints like `getV2Objects` and `postV2ObjectsByObjectRecordsQuery`. Key features include tree-shakeable imports, exponential backoff with `Retry-After` header support, normalized error handling with actionable suggestions, and zero-config setup requiring only an API key. ## Installation ```bash # pnpm (recommended) pnpm add attio-ts-sdk zod # npm npm install attio-ts-sdk zod # yarn yarn add attio-ts-sdk zod ``` ## createAttioClient Creates a configured Attio API client with built-in retries, caching, and error normalization. This is the entry point for all API operations and supports configuration via options or environment variables (`ATTIO_API_KEY`). ```typescript import { createAttioClient } from 'attio-ts-sdk'; // Basic usage with environment variable const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY, }); // Full configuration options const configuredClient = createAttioClient({ apiKey: process.env.ATTIO_API_KEY, baseUrl: 'https://api.attio.com', timeoutMs: 20_000, retry: { maxRetries: 4, initialDelayMs: 500, maxDelayMs: 5000, retryableStatusCodes: [408, 429, 500, 502, 503, 504], respectRetryAfter: true, }, cache: { enabled: true, key: 'my-app', }, responseStyle: 'fields', throwOnError: true, }); ``` ## createRecord Creates a new record in an Attio object (people, companies, deals, etc.). Returns the normalized record with consistent field structure regardless of API response variations. ```typescript import { createAttioClient, createRecord } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Create a new company record const company = await createRecord({ client, object: 'companies', values: { name: [{ value: 'Acme Corporation' }], domains: [{ domain: 'acme.com' }], description: [{ value: 'Leading technology provider' }], }, }); console.log(company.id); // Output: { object_id: '...', record_id: '...' } // Create a person record const person = await createRecord({ client, object: 'people', values: { first_name: [{ value: 'Jane' }], last_name: [{ value: 'Doe' }], email_addresses: [{ email_address: 'jane@acme.com' }], }, }); ``` ## queryRecords Queries records from an Attio object with support for filtering, sorting, pagination, and limits. Returns an array of normalized records. ```typescript import { createAttioClient, queryRecords } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Query companies with filtering and sorting const companies = await queryRecords({ client, object: 'companies', filter: { attribute: 'stage', value: 'customer', }, sorts: [{ attribute: 'created_at', direction: 'desc' }], limit: 25, offset: 0, }); console.log(`Found ${companies.length} companies`); companies.forEach((company) => { console.log(company.values.name); }); // Query people without filters const allPeople = await queryRecords({ client, object: 'people', limit: 100, }); ``` ## upsertRecord Creates or updates a record based on a matching attribute. If a record with the matching attribute value exists, it updates; otherwise, it creates a new record. ```typescript import { createAttioClient, upsertRecord } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Upsert company by domain - creates if not exists, updates if exists const company = await upsertRecord({ client, object: 'companies', matchingAttribute: 'domains', values: { name: [{ value: 'Acme Corp' }], domains: [{ domain: 'acme.com' }], description: [{ value: 'Updated company description' }], industry: [{ value: 'Technology' }], }, }); console.log('Upserted company:', company.id.record_id); ``` ## getRecord Retrieves a single record by its ID from a specified object. ```typescript import { createAttioClient, getRecord } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const company = await getRecord({ client, object: 'companies', recordId: 'abc-123-def-456', }); console.log('Company name:', company.values.name); console.log('Created at:', company.created_at); ``` ## updateRecord Updates an existing record with new values. Only the specified fields are updated; other fields remain unchanged. ```typescript import { createAttioClient, updateRecord } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const updatedCompany = await updateRecord({ client, object: 'companies', recordId: 'abc-123-def-456', values: { description: [{ value: 'New description for the company' }], employee_count: [{ value: 500 }], }, }); console.log('Updated:', updatedCompany.id.record_id); ``` ## deleteRecord Permanently deletes a record from an Attio object. Returns `true` on successful deletion. ```typescript import { createAttioClient, deleteRecord } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const deleted = await deleteRecord({ client, object: 'companies', recordId: 'abc-123-def-456', }); console.log('Deleted:', deleted); // true ``` ## searchRecords Searches across multiple objects using a text query. Useful for finding records by name, email, domain, or other searchable attributes. ```typescript import { createAttioClient, searchRecords } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Search across companies and people const results = await searchRecords({ client, query: 'acme', objects: ['companies', 'people'], limit: 20, }); results.forEach((record) => { console.log(`Found: ${record.id.object_id} - ${record.id.record_id}`); }); // Search with workspace member context const memberResults = await searchRecords({ client, query: 'john@example.com', objects: ['people'], requestAs: { type: 'workspace-member', email_address: 'admin@company.com', }, }); ``` ## listLists Retrieves all lists in the workspace. Lists in Attio are collections of records with custom attributes (like sales pipelines, project boards, etc.). ```typescript import { createAttioClient, listLists } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const lists = await listLists({ client }); lists.forEach((list) => { console.log(`List: ${list.name} (${list.api_slug})`); }); ``` ## queryListEntries Queries entries within a specific list with optional filtering and pagination. Each entry represents a record in the list with list-specific attribute values. ```typescript import { createAttioClient, queryListEntries } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Query entries in a sales pipeline list const entries = await queryListEntries({ client, list: 'sales-pipeline', filter: { attribute: 'stage', value: 'negotiation', }, limit: 50, }); entries.forEach((entry) => { console.log(`Entry: ${entry.id.entry_id}`); console.log(`Parent record: ${entry.parent_record_id}`); }); ``` ## addListEntry Adds a record to a list with optional list-specific attribute values. ```typescript import { createAttioClient, addListEntry } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Add a company to a sales pipeline const entry = await addListEntry({ client, list: 'sales-pipeline', parentRecordId: 'company-record-id-123', entryValues: { stage: [{ status: 'prospecting' }], deal_value: [{ currency_value: 50000 }], expected_close_date: [{ value: '2024-12-31' }], }, }); console.log('Added entry:', entry.id.entry_id); ``` ## updateListEntry Updates list-specific attribute values for an existing list entry. ```typescript import { createAttioClient, updateListEntry } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const updatedEntry = await updateListEntry({ client, list: 'sales-pipeline', entryId: 'entry-id-123', entryValues: { stage: [{ status: 'closed-won' }], deal_value: [{ currency_value: 75000 }], }, }); console.log('Updated entry stage'); ``` ## removeListEntry Removes a record from a list. The underlying record is not deleted, only removed from the list. ```typescript import { createAttioClient, removeListEntry } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const removed = await removeListEntry({ client, list: 'sales-pipeline', entryId: 'entry-id-123', }); console.log('Removed:', removed); // true ``` ## createNote Creates a note attached to a record. Notes can have a title and rich text content. ```typescript import { createAttioClient, createNote } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const note = await createNote({ client, parentObject: 'companies', parentRecordId: 'company-record-id-123', title: 'Q4 Planning Meeting', content: 'Discussed roadmap priorities:\n- Feature A launch\n- Integration with Partner B\n- Hiring 3 engineers', }); console.log('Created note:', note.id.note_id); ``` ## listNotes Retrieves all notes in the workspace. ```typescript import { createAttioClient, listNotes } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const notes = await listNotes({ client }); notes.forEach((note) => { console.log(`Note: ${note.title} - ${note.parent_object}/${note.parent_record_id}`); }); ``` ## createTask Creates a task with optional deadline and linked records. ```typescript import { createAttioClient, createTask } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const task = await createTask({ client, data: { content: 'Follow up on proposal with Acme Corp', deadline_at: '2024-12-31T17:00:00Z', linked_records: [ { target_object: 'companies', target_record_id: 'company-id-123' }, { target_object: 'people', target_record_id: 'person-id-456' }, ], }, }); console.log('Created task:', task.id.task_id); ``` ## updateTask Updates a task's content, deadline, completion status, or other properties. ```typescript import { createAttioClient, updateTask } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Mark task as complete const completedTask = await updateTask({ client, taskId: 'task-id-123', data: { is_completed: true, }, }); // Update task content and deadline const updatedTask = await updateTask({ client, taskId: 'task-id-456', data: { content: 'Updated task description', deadline_at: '2025-01-15T10:00:00Z', }, }); ``` ## listTasks Retrieves all tasks in the workspace. ```typescript import { createAttioClient, listTasks } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const tasks = await listTasks({ client }); const pendingTasks = tasks.filter((task) => !task.is_completed); console.log(`${pendingTasks.length} pending tasks`); ``` ## paginate Automatically handles cursor-based pagination for any endpoint that returns paginated results. Collects all pages into a single array. ```typescript import { createAttioClient, paginate, getV2Meetings } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // Paginate through all meetings const allMeetings = await paginate(async (cursor) => { const result = await getV2Meetings({ client, query: { cursor }, }); return result; }); console.log(`Retrieved ${allMeetings.length} total meetings`); // With pagination limits const limitedMeetings = await paginate( async (cursor) => getV2Meetings({ client, query: { cursor } }), { maxPages: 5, maxItems: 100, } ); ``` ## getAttributeOptions Retrieves select options for a specific attribute. Useful for validating select field values before creating/updating records. ```typescript import { createAttioClient, getAttributeOptions } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const stageOptions = await getAttributeOptions({ client, target: 'objects', identifier: 'companies', attribute: 'stage', }); stageOptions.forEach((option) => { console.log(`Option: ${option.title} (${option.id.option_id})`); }); ``` ## getAttributeStatuses Retrieves status options for a status-type attribute. Statuses are similar to select options but represent workflow states. ```typescript import { createAttioClient, getAttributeStatuses } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const statuses = await getAttributeStatuses({ client, target: 'lists', identifier: 'sales-pipeline', attribute: 'stage', }); statuses.forEach((status) => { console.log(`Status: ${status.title} - ${status.is_archived ? 'Archived' : 'Active'}`); }); ``` ## listAttributes Retrieves all attributes for an object or list. Useful for discovering the schema of an object. ```typescript import { createAttioClient, listAttributes } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const attributes = await listAttributes({ client, target: 'objects', identifier: 'companies', }); attributes.forEach((attr) => { console.log(`${attr.api_slug}: ${attr.type} (${attr.is_required ? 'required' : 'optional'})`); }); ``` ## listWorkspaceMembers Retrieves all members of the current workspace. ```typescript import { createAttioClient, listWorkspaceMembers } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const members = await listWorkspaceMembers({ client }); members.forEach((member) => { console.log(`${member.first_name} ${member.last_name} - ${member.email_address}`); }); ``` ## runBatch Executes multiple async operations with controlled concurrency. Useful for bulk operations with rate limit awareness. ```typescript import { createAttioClient, createRecord, runBatch } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); const companies = [ { name: 'Company A', domain: 'company-a.com' }, { name: 'Company B', domain: 'company-b.com' }, { name: 'Company C', domain: 'company-c.com' }, ]; const results = await runBatch( companies.map((company) => ({ label: company.name, run: async () => createRecord({ client, object: 'companies', values: { name: [{ value: company.name }], domains: [{ domain: company.domain }], }, }), })), { concurrency: 2, // Max 2 concurrent requests stopOnError: false, // Continue on failures } ); const successful = results.filter((r) => r.status === 'fulfilled'); const failed = results.filter((r) => r.status === 'rejected'); console.log(`Created ${successful.length}, Failed ${failed.length}`); ``` ## Error Handling with AttioError All SDK errors are normalized to typed error classes with consistent structure, including request IDs, status codes, and actionable suggestions. ```typescript import { createAttioClient, createRecord, AttioError, AttioApiError, AttioNetworkError, AttioConfigError, } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); try { await createRecord({ client, object: 'companies', values: { stage: [{ value: 'Invalid Stage' }], }, }); } catch (error) { if (error instanceof AttioApiError) { console.error('API Error:', error.message); console.error('Status:', error.status); console.error('Request ID:', error.requestId); console.error('Suggestions:', error.suggestions); } else if (error instanceof AttioNetworkError) { console.error('Network Error:', error.message); console.error('Is retryable:', error.isNetworkError); } else if (error instanceof AttioConfigError) { console.error('Config Error:', error.message); console.error('Code:', error.code); } } ``` ## Direct Generated Endpoint Access Access the full Attio API directly through generated OpenAPI endpoints for complete spec coverage and advanced use cases. ```typescript import { createAttioClient, getV2Objects, postV2ObjectsByObjectRecordsQuery, postV2Webhooks, getV2Webhooks, } from 'attio-ts-sdk'; const client = createAttioClient({ apiKey: process.env.ATTIO_API_KEY }); // List all objects const { data: objects } = await getV2Objects({ client }); console.log('Available objects:', objects); // Query records with full control const { data: people } = await postV2ObjectsByObjectRecordsQuery({ client, path: { object: 'people' }, body: { limit: 10, sorts: [{ attribute: 'created_at', direction: 'desc' }], filter: { attribute: 'email_addresses', condition: 'not_empty', }, }, }); // Create a webhook const { data: webhook } = await postV2Webhooks({ client, body: { data: { target_url: 'https://your-app.com/webhooks/attio', subscriptions: [ { event_type: 'record.created', filter: { object: 'companies' } }, { event_type: 'record.updated', filter: { object: 'companies' } }, { event_type: 'record.deleted', filter: { object: 'companies' } }, ], }, }, }); // List webhooks const { data: webhooks } = await getV2Webhooks({ client }); ``` ## Summary The Attio TypeScript SDK is designed for building CRM integrations, sales automation tools, and customer data platforms. Common use cases include syncing customer data between systems, automating sales pipeline management, building custom dashboards and reports, creating webhook handlers for real-time updates, and bulk importing/exporting records. The high-level helper functions provide a streamlined experience for standard operations while the generated endpoints offer full API coverage for advanced scenarios. Integration patterns typically involve creating a singleton client instance at application startup, using the helper functions for common CRUD operations, leveraging `runBatch` for bulk operations with rate limit handling, implementing webhook endpoints for real-time data synchronization, and using `paginate` for exhaustive data retrieval. The SDK's built-in retry logic, caching, and error normalization reduce boilerplate code and improve reliability in production environments.