code icon Code

Query Attio People

Query People records from Attio with filters and sorts, handling pagination automatically

Source Code

import fs from "fs";

const [
  filterJson = "",
  sortsJson = "",
  maxResults = "50",
  outputPath = "session/attio-people.json",
] = process.argv.slice(2);

const maxResultsNum = Math.min(parseInt(maxResults) || 50, 500);

// Parse filter and sorts if provided
let filter = null;
let sorts = null;

if (filterJson) {
  try {
    filter = JSON.parse(filterJson);
  } catch (e) {
    console.error("Error parsing filter JSON:", e.message);
    process.exit(1);
  }
}

if (sortsJson) {
  try {
    sorts = JSON.parse(sortsJson);
  } catch (e) {
    console.error("Error parsing sorts JSON:", e.message);
    process.exit(1);
  }
}

console.log(`Querying Attio People...`);
if (filter) console.log(`  Filter: ${JSON.stringify(filter)}`);
if (sorts) console.log(`  Sort: ${JSON.stringify(sorts)}`);

const ATTIO_API = "https://api.attio.com/v2";
const headers = {
  Authorization: "Bearer PLACEHOLDER_TOKEN",
  "Content-Type": "application/json",
};

/**
 * Extract readable value from an Attio attribute value
 */
function extractAttributeValue(attr) {
  if (!attr || !Array.isArray(attr) || attr.length === 0) return null;

  // Attio returns attribute values as arrays of value objects
  const values = attr.map((v) => {
    if (v.value !== undefined) return v.value;
    if (v.full_name !== undefined) return v.full_name;
    if (v.first_name !== undefined)
      return `${v.first_name || ""} ${v.last_name || ""}`.trim();
    if (v.email_address !== undefined) return v.email_address;
    if (v.phone_number !== undefined) return v.phone_number;
    if (v.original_phone_number !== undefined) return v.original_phone_number;
    if (v.line_1 !== undefined) {
      // Location
      return [v.line_1, v.locality, v.region, v.country_code]
        .filter(Boolean)
        .join(", ");
    }
    if (v.target_object !== undefined) {
      // Record reference (linked record)
      return { id: v.target_record_id, object: v.target_object };
    }
    if (v.referenced_actor_id !== undefined) {
      // Actor reference
      return v.referenced_actor_id;
    }
    if (v.option !== undefined) {
      // Select/status option
      return v.option;
    }
    // Fallback: return the whole object if we can't extract
    return v;
  });

  // Return single value if only one, otherwise array
  return values.length === 1 ? values[0] : values;
}

try {
  const records = [];
  let cursor = undefined;
  let totalFetched = 0;

  while (totalFetched < maxResultsNum) {
    const pageSize = Math.min(maxResultsNum - totalFetched, 100);

    const body = {
      limit: pageSize,
      ...(filter && { filter }),
      ...(sorts && { sorts }),
      ...(cursor && { offset: cursor }),
    };

    const queryRes = await fetch(`${ATTIO_API}/objects/people/records/query`, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
    });

    if (!queryRes.ok) {
      const errorText = await queryRes.text();
      console.error(`Query failed: ${queryRes.status}`);
      console.error(errorText);
      throw new Error(`Query failed: ${queryRes.status}`);
    }

    const response = await queryRes.json();

    for (const record of response.data || []) {
      if (totalFetched >= maxResultsNum) break;

      const entry = {
        id: record.id?.record_id,
        createdAt: record.created_at,
        values: {},
      };

      // Extract all attribute values
      for (const [slug, attrValue] of Object.entries(record.values || {})) {
        entry.values[slug] = extractAttributeValue(attrValue);
      }

      records.push(entry);
      totalFetched++;
    }

    // Check for more pages
    if (!response.next_page_offset || totalFetched >= maxResultsNum) break;
    cursor = response.next_page_offset;

    console.log(`  Fetched ${totalFetched} records...`);
  }

  // Ensure output directory exists
  const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
  if (dir) {
    fs.mkdirSync(dir, { recursive: true });
  }

  const output = {
    object: "people",
    filter: filter || null,
    sorts: sorts || null,
    queriedAt: new Date().toISOString(),
    count: records.length,
    records,
  };

  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  // Log summary
  console.log(`\nāœ“ Found ${records.length} people`);
  console.log(`  Written to: ${outputPath}`);

  // Show first few records
  if (records.length > 0) {
    console.log(`\n  Sample records:`);
    for (const record of records.slice(0, 3)) {
      const name =
        record.values.name ||
        record.values.first_name ||
        record.values.full_name ||
        "Unknown";
      const email = record.values.email_addresses || "";
      console.log(`    šŸ‘¤ ${name}${email ? ` (${email})` : ""}`);
    }
    if (records.length > 3) {
      console.log(`    ... and ${records.length - 3} more`);
    }
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      count: records.length,
    })
  );
} catch (error) {
  console.error("Error querying Attio:", error.message);
  throw error;
}
                  import fs from "fs";

const [
  filterJson = "",
  sortsJson = "",
  maxResults = "50",
  outputPath = "session/attio-people.json",
] = process.argv.slice(2);

const maxResultsNum = Math.min(parseInt(maxResults) || 50, 500);

// Parse filter and sorts if provided
let filter = null;
let sorts = null;

if (filterJson) {
  try {
    filter = JSON.parse(filterJson);
  } catch (e) {
    console.error("Error parsing filter JSON:", e.message);
    process.exit(1);
  }
}

if (sortsJson) {
  try {
    sorts = JSON.parse(sortsJson);
  } catch (e) {
    console.error("Error parsing sorts JSON:", e.message);
    process.exit(1);
  }
}

console.log(`Querying Attio People...`);
if (filter) console.log(`  Filter: ${JSON.stringify(filter)}`);
if (sorts) console.log(`  Sort: ${JSON.stringify(sorts)}`);

const ATTIO_API = "https://api.attio.com/v2";
const headers = {
  Authorization: "Bearer PLACEHOLDER_TOKEN",
  "Content-Type": "application/json",
};

/**
 * Extract readable value from an Attio attribute value
 */
function extractAttributeValue(attr) {
  if (!attr || !Array.isArray(attr) || attr.length === 0) return null;

  // Attio returns attribute values as arrays of value objects
  const values = attr.map((v) => {
    if (v.value !== undefined) return v.value;
    if (v.full_name !== undefined) return v.full_name;
    if (v.first_name !== undefined)
      return `${v.first_name || ""} ${v.last_name || ""}`.trim();
    if (v.email_address !== undefined) return v.email_address;
    if (v.phone_number !== undefined) return v.phone_number;
    if (v.original_phone_number !== undefined) return v.original_phone_number;
    if (v.line_1 !== undefined) {
      // Location
      return [v.line_1, v.locality, v.region, v.country_code]
        .filter(Boolean)
        .join(", ");
    }
    if (v.target_object !== undefined) {
      // Record reference (linked record)
      return { id: v.target_record_id, object: v.target_object };
    }
    if (v.referenced_actor_id !== undefined) {
      // Actor reference
      return v.referenced_actor_id;
    }
    if (v.option !== undefined) {
      // Select/status option
      return v.option;
    }
    // Fallback: return the whole object if we can't extract
    return v;
  });

  // Return single value if only one, otherwise array
  return values.length === 1 ? values[0] : values;
}

try {
  const records = [];
  let cursor = undefined;
  let totalFetched = 0;

  while (totalFetched < maxResultsNum) {
    const pageSize = Math.min(maxResultsNum - totalFetched, 100);

    const body = {
      limit: pageSize,
      ...(filter && { filter }),
      ...(sorts && { sorts }),
      ...(cursor && { offset: cursor }),
    };

    const queryRes = await fetch(`${ATTIO_API}/objects/people/records/query`, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
    });

    if (!queryRes.ok) {
      const errorText = await queryRes.text();
      console.error(`Query failed: ${queryRes.status}`);
      console.error(errorText);
      throw new Error(`Query failed: ${queryRes.status}`);
    }

    const response = await queryRes.json();

    for (const record of response.data || []) {
      if (totalFetched >= maxResultsNum) break;

      const entry = {
        id: record.id?.record_id,
        createdAt: record.created_at,
        values: {},
      };

      // Extract all attribute values
      for (const [slug, attrValue] of Object.entries(record.values || {})) {
        entry.values[slug] = extractAttributeValue(attrValue);
      }

      records.push(entry);
      totalFetched++;
    }

    // Check for more pages
    if (!response.next_page_offset || totalFetched >= maxResultsNum) break;
    cursor = response.next_page_offset;

    console.log(`  Fetched ${totalFetched} records...`);
  }

  // Ensure output directory exists
  const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
  if (dir) {
    fs.mkdirSync(dir, { recursive: true });
  }

  const output = {
    object: "people",
    filter: filter || null,
    sorts: sorts || null,
    queriedAt: new Date().toISOString(),
    count: records.length,
    records,
  };

  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  // Log summary
  console.log(`\nāœ“ Found ${records.length} people`);
  console.log(`  Written to: ${outputPath}`);

  // Show first few records
  if (records.length > 0) {
    console.log(`\n  Sample records:`);
    for (const record of records.slice(0, 3)) {
      const name =
        record.values.name ||
        record.values.first_name ||
        record.values.full_name ||
        "Unknown";
      const email = record.values.email_addresses || "";
      console.log(`    šŸ‘¤ ${name}${email ? ` (${email})` : ""}`);
    }
    if (records.length > 3) {
      console.log(`    ... and ${records.length - 3} more`);
    }
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      count: records.length,
    })
  );
} catch (error) {
  console.error("Error querying Attio:", error.message);
  throw error;
}