Skillshub attio-upgrade-migration

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/attio-upgrade-migration" ~/.claude/skills/comeonoliver-skillshub-attio-upgrade-migration && rm -rf "$T"
manifest: skills/jeremylongshore/claude-code-plugins-plus-skills/attio-upgrade-migration/SKILL.md
source content

Attio Upgrade & Migration

Overview

Attio has two API generations: v1 (legacy, deprecated) and v2 (current). This skill covers the v1-to-v2 migration, community SDK upgrade paths, and how to detect and adapt to API changes since Attio does not publish a traditional SDK changelog.

V1 to V2 Migration Reference

Endpoint Changes

OperationV1 EndpointV2 Endpoint
List objects
GET /v1/objects
GET /v2/objects
Query records
GET /v1/objects/{id}/records
POST /v2/objects/{slug}/records/query
Create record
POST /v1/objects/{id}/records
POST /v2/objects/{slug}/records
Get record
GET /v1/objects/{id}/records/{rid}
GET /v2/objects/{slug}/records/{rid}
List entries
GET /v1/lists/{id}/entries
POST /v2/lists/{slug}/entries/query
Create webhook
POST /v1/webhooks
POST /v2/webhooks
SearchN/A
POST /v2/records/search

Key Differences

AspectV1V2
IdentifiersUUIDs onlySlugs (preferred) or UUIDs
Record queryGET with query paramsPOST with JSON body (filters, sorts)
FilteringBasic query paramsRich operators (
$eq
,
$contains
,
$gt
,
$and
,
$or
)
Pagination
page
+
per_page
limit
+
offset
or cursor-based
Webhook payloadsCustom formatConsistent with v2 response shapes
Webhook filteringNoneEvent-type and attribute-level filters

Step 1: Update Base URL

// Before
const BASE = "https://api.attio.com/v1";

// After
const BASE = "https://api.attio.com/v2";

Step 2: Migrate Record Queries

// V1: GET with query params
const v1 = await fetch(
  `${BASE}/objects/${objectId}/records?page=1&per_page=50`,
  { headers: { Authorization: `Bearer ${token}` } }
);

// V2: POST with filter body, using slug instead of UUID
const v2 = await fetch(
  `${BASE}/objects/people/records/query`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      filter: {
        email_addresses: { email_address: { $contains: "@example.com" } },
      },
      sorts: [{ attribute: "created_at", field: "created_at", direction: "desc" }],
      limit: 50,
      offset: 0,
    }),
  }
);

Step 3: Update Record Creation

// V1: values as flat key-value pairs
const v1Body = {
  name: "Ada Lovelace",
  email: "ada@example.com",
};

// V2: values nested under data.values, always arrays
const v2Body = {
  data: {
    values: {
      name: [{ first_name: "Ada", last_name: "Lovelace", full_name: "Ada Lovelace" }],
      email_addresses: ["ada@example.com"],
    },
  },
};

Step 4: Migrate Webhooks from V1 to V2

// V1 webhook event types
"object.record.created"

// V2 webhook event types
"record.created"
"record.updated"
"record.deleted"
"record.merged"
"list-entry.created"
"note.created"
"task.created"
// ... plus filtering support

V2 webhooks support event filtering to reduce volume:

await client.post("/webhooks", {
  target_url: "https://yourapp.com/webhooks/attio",
  subscriptions: [
    {
      event_type: "record.updated",
      filter: {
        // Only trigger for updates to the "stage" attribute on deals
        $and: [
          { object: { $eq: "deals" } },
          { attribute: { $eq: "stage" } },
        ],
      },
    },
  ],
});

Community SDK Migration

Since there is no official Attio SDK, you may be using community packages:

attio-js (most popular community SDK)

# Check current version
npm list attio-js

# Upgrade
npm install attio-js@latest
// attio-js uses the v2 API natively
import { AttioClient } from "attio-js";

const client = new AttioClient({ accessToken: process.env.ATTIO_API_KEY });
const people = await client.records.query("people", { limit: 10 });

Direct fetch (recommended for control)

No upgrade risk -- you control the endpoint URLs directly. See

attio-sdk-patterns
for a typed wrapper.

Detecting API Changes

Attio does not publish a traditional changelog for the REST API. Monitor for changes:

// Save the OpenAPI spec hash and check periodically
import crypto from "crypto";

async function checkForApiChanges(): Promise<boolean> {
  const spec = await fetch("https://docs.attio.com/openapi.json").then(r => r.text());
  const hash = crypto.createHash("sha256").update(spec).digest("hex");

  const previousHash = await readStoredHash(); // From file or DB
  if (previousHash && hash !== previousHash) {
    console.warn("Attio OpenAPI spec changed! Review for breaking changes.");
    await storeHash(hash);
    return true;
  }
  await storeHash(hash);
  return false;
}

Migration Checklist

[ ] Base URL updated to /v2
[ ] Object references use slugs instead of UUIDs where possible
[ ] Record queries migrated from GET to POST with filter body
[ ] Record creation uses data.values wrapper with arrays
[ ] Webhook subscriptions recreated with v2 event types
[ ] Webhook handlers updated for v2 payload format
[ ] Pagination migrated from page/per_page to limit/offset
[ ] Error handling updated for v2 error response format
[ ] Tests updated and passing against v2 endpoints
[ ] OpenAPI spec monitoring configured for future changes

Error Handling

Migration issueSymptomFix
Old v1 URL404 on all callsUpdate base URL to
/v2
UUID instead of slug404 on object endpointsUse
api_slug
from
GET /v2/objects
Flat values (v1 format)422 validation errorWrap in
{ data: { values: { ... } } }
Old webhook event typesWebhook never firesRecreate with v2 event types
Old pagination paramsIgnored, only first page returnedSwitch to
limit
+
offset

Resources

Next Steps

For CI integration during upgrades, see

attio-ci-integration
.