Skills Development Attio API Integration Patterns Guide

Attio API Integration Patterns Guide

v20260423
attio-sdk-patterns
This guide provides a set of production-grade TypeScript patterns for robust interaction with the Attio REST API. It covers essential best practices including typed clients, automatic retry mechanisms with exponential backoff, cursor-based pagination iterators, and multi-tenant factory implementation, ensuring reliable and scalable API integration. Use these patterns to handle common API challenges like rate limiting and large datasets efficiently.
Get Skill
437 downloads
Overview

Attio SDK Patterns

Overview

There is no official Attio Node.js SDK. The API is a clean REST/JSON interface at https://api.attio.com/v2. These patterns wrap fetch into a production-grade typed client with retry, pagination, and error normalization.

Prerequisites

  • Node.js 18+ (native fetch)
  • TypeScript 5+
  • Completed attio-install-auth

Instructions

Pattern 1: Typed Client with Error Normalization

// src/attio/client.ts
const ATTIO_BASE = "https://api.attio.com/v2";

export class AttioApiError extends Error {
  constructor(
    public statusCode: number,
    public type: string,
    public code: string,
    message: string
  ) {
    super(message);
    this.name = "AttioApiError";
  }

  get retryable(): boolean {
    return this.statusCode === 429 || this.statusCode >= 500;
  }
}

export class AttioClient {
  constructor(private apiKey: string) {}

  async request<T>(
    method: string,
    path: string,
    body?: Record<string, unknown>
  ): Promise<T> {
    const res = await fetch(`${ATTIO_BASE}${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new AttioApiError(
        res.status,
        err.type || "unknown",
        err.code || "unknown",
        err.message || `HTTP ${res.status}`
      );
    }

    return res.json() as Promise<T>;
  }

  // Convenience methods for common HTTP verbs
  get<T>(path: string) { return this.request<T>("GET", path); }
  post<T>(path: string, body: Record<string, unknown>) { return this.request<T>("POST", path, body); }
  patch<T>(path: string, body: Record<string, unknown>) { return this.request<T>("PATCH", path, body); }
  put<T>(path: string, body: Record<string, unknown>) { return this.request<T>("PUT", path, body); }
  delete<T>(path: string) { return this.request<T>("DELETE", path); }
}

Pattern 2: Retry with Exponential Backoff

// src/attio/retry.ts
export async function withRetry<T>(
  operation: () => Promise<T>,
  config = { maxRetries: 4, baseMs: 1000, maxMs: 30000 }
): Promise<T> {
  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (err) {
      if (attempt === config.maxRetries) throw err;

      // Only retry on rate limits (429) and server errors (5xx)
      if (err instanceof AttioApiError && !err.retryable) throw err;

      const delay = Math.min(
        config.baseMs * Math.pow(2, attempt) + Math.random() * 500,
        config.maxMs
      );
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("Unreachable");
}

// Usage
const people = await withRetry(() =>
  client.post("/objects/people/records/query", { limit: 50 })
);

Pattern 3: Cursor-Based Pagination Iterator

Attio uses cursor-based pagination. The initial request omits offset; responses include pagination.next_cursor.

// src/attio/paginate.ts
export async function* paginate<T>(
  client: AttioClient,
  path: string,
  body: Record<string, unknown> = {},
  pageSize = 100
): AsyncGenerator<T> {
  let offset = 0;
  let hasMore = true;

  while (hasMore) {
    const res = await withRetry(() =>
      client.post<{ data: T[] }>(path, {
        ...body,
        limit: pageSize,
        offset,
      })
    );

    for (const item of res.data) {
      yield item;
    }

    hasMore = res.data.length === pageSize;
    offset += pageSize;
  }
}

// Usage: iterate all companies
for await (const company of paginate(client, "/objects/companies/records/query")) {
  console.log(company);
}

Pattern 4: Singleton with Lazy Init

// src/attio/singleton.ts
let _client: AttioClient | null = null;

export function getClient(): AttioClient {
  if (!_client) {
    const key = process.env.ATTIO_API_KEY;
    if (!key) throw new Error("ATTIO_API_KEY not set");
    _client = new AttioClient(key);
  }
  return _client;
}

Pattern 5: Multi-Tenant Factory

// src/attio/factory.ts
const tenantClients = new Map<string, AttioClient>();

export function getClientForTenant(tenantId: string): AttioClient {
  if (!tenantClients.has(tenantId)) {
    const key = getTenantApiKey(tenantId); // from DB or secrets manager
    tenantClients.set(tenantId, new AttioClient(key));
  }
  return tenantClients.get(tenantId)!;
}

Pattern 6: Response Validation with Zod

import { z } from "zod";

const AttioPersonSchema = z.object({
  id: z.object({
    object_id: z.string(),
    record_id: z.string(),
  }),
  created_at: z.string(),
  values: z.object({
    name: z.array(z.object({
      first_name: z.string().nullable(),
      last_name: z.string().nullable(),
      full_name: z.string().nullable(),
    })),
    email_addresses: z.array(z.object({
      email_address: z.string(),
    })),
  }).passthrough(),
});

// Validated fetch
const raw = await client.post("/objects/people/records/query", { limit: 1 });
const person = AttioPersonSchema.parse(raw.data[0]);

Error Handling

PatternWhen to UseBenefit
AttioApiError classAll API callsTyped error with retryable flag
withRetry wrapperAny mutating or critical readAuto-retry on 429/5xx
Zod validationParsing API responsesCatches schema drift at runtime
Multi-tenant factorySaaS with per-customer tokensIsolates credentials

Resources

Next Steps

Apply these patterns in attio-core-workflow-a (records CRUD) and attio-core-workflow-b (lists and entries).

Info
Category Development
Name attio-sdk-patterns
Version v20260423
Size 6.35KB
Updated At 2026-04-26
Language