> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sixtyfour.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Lookalike Search

> Find people similar to a seed cohort using LinkedIn profiles or a CSV, then page the results by similarity.

Lookalike Search analyzes a seed cohort and finds similar people across Sixtyfour's people index. It infers the cohort's shared professional traits, builds an eligible audience, excludes the seed people, and ranks each match using grounded profile signals and semantic similarity.

<Note>
  Lookalike Search currently supports people only. Company lookalike search is not available yet.
</Note>

```mermaid theme={null}
flowchart LR
  Seeds["LinkedIn URLs or CSV"] --> Start["POST /search/lookalike"]
  Start --> Poll["GET /search/lookalike/{task_id}"]
  Poll --> Results["POST /search/query"]
  Results --> Cursor["Continue with next_cursor"]
```

## Prepare the seed cohort

Every request accepts exactly one seed source.

| Seed source       | Request field            | Requirements                                                                              |
| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------- |
| LinkedIn profiles | `linkedin_urls`          | 1–1,000 LinkedIn profile URLs. Each URL must be 500 characters or fewer.                  |
| Uploaded CSV      | `csv_resource_handle_id` | Handle returned by `POST /storage/csv/upload`. The CSV can contain up to 1,000 data rows. |

For CSV seeds, a LinkedIn profile URL column produces the most reliable identity matches. Recognized headers include `linkedin`, `linkedin url`, `linkedin profile`, and `profile url`. Rows without a LinkedIn URL need a full name plus supporting identity data such as company, company domain, role, location, or education. Sixtyfour drops weak or ambiguous identity matches instead of guessing.

Use `guidance` only for requirements that are not implied by the cohort, such as `"Only people in the United States with 8+ years of experience"`. Sixtyfour infers roles, skills, industries, seniority, employers, and other shared traits from the seeds.

`analysis_sample_size` controls the maximum number of representative resolved profiles analyzed for cohort traits. It defaults to 100 and accepts values from 1 to 100.

## Run a lookalike search

Submit the seed cohort, poll the dedicated status endpoint until the search completes, then pass its `search_id` to `/search/query`.

<CodeGroup>
  ```bash cURL theme={null}
  API_KEY="YOUR_API_KEY"
  BASE_URL="https://api.sixtyfour.ai"

  START_RESPONSE=$(curl -fsS -X POST "$BASE_URL/search/lookalike" \
    -H "x-api-key: $API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "linkedin_urls": [
        "https://www.linkedin.com/in/ada-lovelace",
        "https://www.linkedin.com/in/grace-hopper"
      ],
      "guidance": "Only people in the United States"
    }')

  TASK_ID=$(printf '%s' "$START_RESPONSE" | jq -r '.task_id')
  DEADLINE=$(( $(date +%s) + 30 * 60 ))

  while true; do
    STATUS_RESPONSE=$(curl -fsS --connect-timeout 10 --max-time 30 \
      "$BASE_URL/search/lookalike/$TASK_ID?include_filters=false&include_analysis=false" \
      -H "x-api-key: $API_KEY")
    STATUS=$(printf '%s' "$STATUS_RESPONSE" | jq -r '.status')

    if [ "$STATUS" = "completed" ]; then
      SEARCH_ID=$(printf '%s' "$STATUS_RESPONSE" | jq -r '.search_id')
      break
    fi

    case "$STATUS" in
      failed|cancelled|terminated|timed_out)
        printf '%s\n' "$STATUS_RESPONSE"
        exit 1
        ;;
    esac

    if [ "$(date +%s)" -ge "$DEADLINE" ]; then
      printf '%s\n' "Search did not complete within 30 minutes"
      exit 1
    fi

    sleep 10
  done

  curl -fsS -X POST "$BASE_URL/search/query" \
    -H "x-api-key: $API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"search_id\":\"$SEARCH_ID\",\"page_size\":25}"
  ```

  ```python Python theme={null}
  import time

  import requests

  API_KEY = "YOUR_API_KEY"
  BASE_URL = "https://api.sixtyfour.ai"
  HEADERS = {
      "x-api-key": API_KEY,
      "Content-Type": "application/json",
  }
  TERMINAL_FAILURES = {"failed", "cancelled", "terminated", "timed_out"}

  start_response = requests.post(
      f"{BASE_URL}/search/lookalike",
      headers=HEADERS,
      json={
          "linkedin_urls": [
              "https://www.linkedin.com/in/ada-lovelace",
              "https://www.linkedin.com/in/grace-hopper",
          ],
          "guidance": "Only people in the United States",
      },
  )
  start_response.raise_for_status()
  task_id = start_response.json()["task_id"]
  deadline = time.monotonic() + 30 * 60

  while True:
      status_response = requests.get(
          f"{BASE_URL}/search/lookalike/{task_id}",
          headers=HEADERS,
          params={"include_filters": "false", "include_analysis": "false"},
          timeout=(10, 30),
      )
      status_response.raise_for_status()
      status_data = status_response.json()

      if status_data["status"] == "completed":
          search_id = status_data["search_id"]
          break
      if status_data["status"] in TERMINAL_FAILURES:
          raise RuntimeError(
              status_data.get("error") or f"Search {status_data['status']}"
          )
      if time.monotonic() >= deadline:
          raise TimeoutError("Search did not complete within 30 minutes")

      time.sleep(10)

  page_response = requests.post(
      f"{BASE_URL}/search/query",
      headers=HEADERS,
      json={"search_id": search_id, "page_size": 25},
  )
  page_response.raise_for_status()
  page = page_response.json()

  for person in page["results"]:
      print(
          person["raw_source"].get("fullName"),
          person["lookalike_score"],
          person["lookalike_evidence"],
      )
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = "YOUR_API_KEY";
  const BASE_URL = "https://api.sixtyfour.ai";
  const headers = {
    "x-api-key": API_KEY,
    "Content-Type": "application/json",
  };
  const terminalFailures = new Set([
    "failed",
    "cancelled",
    "terminated",
    "timed_out",
  ]);

  async function requestJson(url, options = {}) {
    const response = await fetch(url, options);
    if (!response.ok) {
      throw new Error(`${response.status}: ${await response.text()}`);
    }
    return response.json();
  }

  async function main() {
    const started = await requestJson(`${BASE_URL}/search/lookalike`, {
      method: "POST",
      headers,
      body: JSON.stringify({
        linkedin_urls: [
          "https://www.linkedin.com/in/ada-lovelace",
          "https://www.linkedin.com/in/grace-hopper",
        ],
        guidance: "Only people in the United States",
      }),
    });

    let searchId;
    const deadline = Date.now() + 30 * 60_000;
    while (!searchId) {
      const params = new URLSearchParams({
        include_filters: "false",
        include_analysis: "false",
      });
      const status = await requestJson(
        `${BASE_URL}/search/lookalike/${started.task_id}?${params}`,
        { headers, signal: AbortSignal.timeout(30_000) },
      );

      if (status.status === "completed") {
        searchId = status.search_id;
        break;
      }
      if (terminalFailures.has(status.status)) {
        throw new Error(status.error ?? `Search ${status.status}`);
      }
      if (Date.now() >= deadline) {
        throw new Error("Search did not complete within 30 minutes");
      }

      await new Promise((resolve) => setTimeout(resolve, 10_000));
    }

    const page = await requestJson(`${BASE_URL}/search/query`, {
      method: "POST",
      headers,
      body: JSON.stringify({ search_id: searchId, page_size: 25 }),
    });

    for (const person of page.results) {
      console.log(
        person.raw_source?.fullName,
        person.lookalike_score,
        person.lookalike_evidence,
      );
    }
  }

  main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
  });
  ```
</CodeGroup>

<Warning>
  Poll lookalike tasks with `GET /search/lookalike/{task_id}`, not `GET /search/status/{task_id}`. If the start request times out, do not submit it again automatically—the search may already be running.
</Warning>

## Start response

The start endpoint returns HTTP 202 after resolving the seeds and launching the search.

```json theme={null}
{
  "task_id": "lookalike_search_11111111-1111-4111-8111-111111111111",
  "search_id": "11111111-1111-4111-8111-111111111111",
  "status": "running",
  "seed_diagnostics": {
    "seed_type": "linkedin_urls",
    "supplied": 2,
    "parsed": 2,
    "resolved": 2,
    "skipped": 0,
    "analyzed": 2,
    "errors": [],
    "warnings": [],
    "identity_resolution": {
      "exact_matches": 2,
      "similarity_matches": 0,
      "dropped_below_threshold": 0,
      "ambiguous_matches": 0,
      "duplicates_removed": 0,
      "average_confidence": 1.0
    }
  }
}
```

Use `seed_diagnostics` to confirm how much of the supplied cohort Sixtyfour resolved and analyzed. The search proceeds when at least one usable seed resolves.

## Completed status

Lookalike searches typically complete in 1–3 minutes. Poll every 10 seconds and stop on `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`.

```json theme={null}
{
  "task_id": "lookalike_search_11111111-1111-4111-8111-111111111111",
  "status": "completed",
  "start_time": "2026-08-12T18:00:00+00:00",
  "close_time": "2026-08-12T18:02:14+00:00",
  "search_id": "11111111-1111-4111-8111-111111111111",
  "total_results": 12480,
  "seed_diagnostics": {
    "seed_type": "linkedin_urls",
    "supplied": 2,
    "parsed": 2,
    "resolved": 2,
    "skipped": 0,
    "analyzed": 2,
    "errors": [],
    "warnings": [],
    "identity_resolution": {
      "exact_matches": 2,
      "similarity_matches": 0,
      "dropped_below_threshold": 0,
      "ambiguous_matches": 0,
      "duplicates_removed": 0,
      "average_confidence": 1.0
    }
  },
  "excluded_public_ids": ["ada-lovelace", "grace-hopper"]
}
```

The status endpoint includes generated `filters` and cohort `analysis` by default. Set `include_filters=false&include_analysis=false` when only progress and the completed `search_id` are needed.

## Page ranked results

Use the completed `search_id` for the first page:

```json theme={null}
{
  "search_id": "11111111-1111-4111-8111-111111111111",
  "page_size": 25
}
```

A lookalike result adds ranking fields to the standard raw people-search row:

```json theme={null}
{
  "search_id": "11111111-1111-4111-8111-111111111111",
  "next_cursor": "eyJ2IjoxLCJraWQiOiJr...",
  "cursor_expires_in_seconds": 1800,
  "has_more": true,
  "page_size": 25,
  "page_count": 25,
  "total_results": 25,
  "total_available": 12480,
  "results": [
    {
      "raw_source": {
        "publicId": "jane-doe",
        "fullName": "Jane Doe",
        "headline": "Staff Data Engineer"
      },
      "lookalike_score": 0.82,
      "lookalike_rank": 1,
      "lookalike_evidence": [
        {
          "signal_id": "skill:python",
          "dimension": "skill",
          "value": "Python",
          "support": 0.78,
          "coverage": 0.91,
          "weight": 0.34
        }
      ]
    }
  ]
}
```

| Field                | Description                                                                                 |
| -------------------- | ------------------------------------------------------------------------------------------- |
| `lookalike_score`    | Normalized similarity score from 0 to 1. It measures fit to this cohort, not a probability. |
| `lookalike_rank`     | Global 1-based rank across the lookalike result set.                                        |
| `lookalike_evidence` | Up to five matched cohort signals, ordered by ranking weight.                               |

For every later page, send only the cursor returned by the previous response:

```json theme={null}
{
  "cursor": "eyJ2IjoxLCJraWQiOiJr..."
}
```

<Warning>
  A page can contain fewer rows than `page_size` while `has_more` is `true`. Continue until `has_more` is `false`; do not infer completion from the number of rows returned.
</Warning>

## Use a CSV seed cohort

Create a CSV with no more than 1,000 data rows:

```csv theme={null}
linkedin_url,name,current_company,current_role
https://www.linkedin.com/in/ada-lovelace,Ada Lovelace,Analytical Engines,Mathematician
https://www.linkedin.com/in/grace-hopper,Grace Hopper,United States Navy,Computer Scientist
```

Upload it, then pass the returned `handle_id` as `csv_resource_handle_id`.

<CodeGroup>
  ```bash cURL theme={null}
  UPLOAD=$(curl -fsS -X POST "https://api.sixtyfour.ai/storage/csv/upload" \
    -H "x-api-key: YOUR_API_KEY" \
    -F "file=@lookalike-seeds.csv;type=text/csv")

  HANDLE_ID=$(printf '%s' "$UPLOAD" | jq -r '.handle_id')

  curl -fsS -X POST "https://api.sixtyfour.ai/search/lookalike" \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"csv_resource_handle_id\":\"$HANDLE_ID\"}"
  ```

  ```python Python theme={null}
  import requests

  headers = {"x-api-key": "YOUR_API_KEY"}

  with open("lookalike-seeds.csv", "rb") as csv_file:
      upload_response = requests.post(
          "https://api.sixtyfour.ai/storage/csv/upload",
          headers=headers,
          files={"file": ("lookalike-seeds.csv", csv_file, "text/csv")},
      )
  upload_response.raise_for_status()
  handle_id = upload_response.json()["handle_id"]

  start_response = requests.post(
      "https://api.sixtyfour.ai/search/lookalike",
      headers={**headers, "Content-Type": "application/json"},
      json={"csv_resource_handle_id": handle_id},
  )
  start_response.raise_for_status()
  print(start_response.json())
  ```

  ```javascript JavaScript theme={null}
  import { readFile } from "node:fs/promises";

  const headers = { "x-api-key": "YOUR_API_KEY" };
  const csv = await readFile("lookalike-seeds.csv");
  const form = new FormData();
  form.append(
    "file",
    new Blob([csv], { type: "text/csv" }),
    "lookalike-seeds.csv",
  );

  const uploadResponse = await fetch(
    "https://api.sixtyfour.ai/storage/csv/upload",
    { method: "POST", headers, body: form },
  );
  if (!uploadResponse.ok) throw new Error(await uploadResponse.text());
  const { handle_id } = await uploadResponse.json();

  const startResponse = await fetch(
    "https://api.sixtyfour.ai/search/lookalike",
    {
      method: "POST",
      headers: { ...headers, "Content-Type": "application/json" },
      body: JSON.stringify({ csv_resource_handle_id: handle_id }),
    },
  );
  if (!startResponse.ok) throw new Error(await startResponse.text());
  console.log(await startResponse.json());
  ```
</CodeGroup>

After the start call, use the same status and result-pagination flow shown above.

## Export results

Pass the completed lookalike `search_id` to `POST /search/export`, poll the returned export task through `GET /search/status/{task_id}`, then download the generated CSV. See [Export Search Results](/api-reference/search/search-endpoints#export-search-results).

## Pricing

See [Credits & Pricing Guide](/guides/credits-and-pricing) for more information.

## Errors

For error responses (400, 402, 403, 404, 422, 429, etc.), see [Handling Errors](/api-reference/errors).
