> ## 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.

# Search Results

> Reference for the endpoints every search shares: task status, paginated results, CSV export and download, and result limits.

Every deep, filter, and lookalike search produces a `search_id`. These endpoints read its results: poll a task with `GET /search/status/{task_id}`, page through rows with `POST /search/query`, build a CSV with `POST /search/export`, download it with `GET /search/download`, and read your result ceiling with `GET /search/limits`.

> [card] [API Reference](https://docs.sixtyfour.ai/api-reference/search/search-query.md)
>
> See the full request/response schema and parameters in the API Reference.

## Get search status

Poll the progress and result of a deep search or an export.

```http theme={null}
GET https://api.sixtyfour.ai/search/status/{task_id}
```

`task_id` is the value returned by [Start Deep Search](https://docs.sixtyfour.ai/api-reference/search/search-endpoints.md#start-deep-search) (`agentic_search_{search_id}`) or by [Export](#export-search-results) (`search_export_{search_id}`). Lookalike tasks use [`GET /search/lookalike/{task_id}`](https://docs.sixtyfour.ai/api-reference/search/lookalike-search#completed-status); this endpoint refuses them.

`GET /search/deep-search-status/{task_id}` is a deprecated alias of this endpoint, past its May 12, 2026 sunset date. It still returns the same response, with `Deprecation` and `Sunset` headers.

### Task statuses

| Status | Description |
| - | - |
| `running` | The task is in progress. `progress_message` describes the current step. |
| `completed` | The task finished. `search_id` and `total_results` are set; `resource_handle_id` is set when a CSV was produced. |
| `failed` | The task stopped. Read `error`. |
| `cancelled`, `terminated`, `timed_out` | The task stopped before finishing. A deep search or export times out after 15 minutes. |

`running` is the only in-progress status. Poll every 10 seconds or slower, and stop on any other status, including values not listed here.

A search that finds nothing does not always end `completed`:

* A `query_only` deep search with no matches ends `completed` with `total_results: 0`.
* A CSV deep search or an export with no rows to write ends `failed`, with a message in `error`. This includes searches whose every row was excluded.

A deep search started above your organization's concurrency limit also ends `failed`. See [Concurrency and run time](https://docs.sixtyfour.ai/api-reference/search/search-endpoints.md#concurrency-and-run-time).

### Status response

| Field | Description |
| - | - |
| `task_id` | The task being polled. |
| `status` | One of the [task statuses](#task-statuses). |
| `search_id` | The search this task belongs to, in every status. Pass it to `POST /search/query` or `POST /search/export` once the task is `completed`. |
| `start_time`, `close_time` | ISO 8601 timestamps. `close_time` is set once the task stops. |
| `progress_message` | Human-readable progress while `running`. |
| `current_phase` | Progress step number while `running`. |
| `iterations` | While a deep search is `running`, its latest search iteration: `iteration`, `results`, `elapsed_ms`, and `message`. |
| `total_results` | Matches found. For an export, rows exported. |
| `exported_count` | Rows written to the CSV. For a `query_only` deep search, rows the search collected, up to `max_results`. |
| `resource_handle_id` | The CSV's handle. Pass it to `GET /search/download`. `null` for a `query_only` deep search. |
| `error` | Failure reason. |

Fields without a value are returned as `null`.

```json theme={null}
{
  "task_id": "agentic_search_e3b0c442-98fc-1c14-9afb-f4c8996fb924",
  "status": "completed",
  "start_time": "2026-01-15T18:00:00+00:00",
  "current_phase": null,
  "progress_message": null,
  "close_time": "2026-01-15T18:04:12+00:00",
  "resource_handle_id": "8f14e45f-1111-2222-3333-abcdefabcdef",
  "total_results": 2140,
  "exported_count": 500,
  "error": null,
  "search_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
  "iterations": null
}
```

For a complete polling loop that honors `Retry-After` and stops at a deadline, see [Deep Search → Example usage](https://docs.sixtyfour.ai/api-reference/search/search-endpoints.md#example-usage).

***

## Query search results

Page through the results of a completed search as JSON.

```http theme={null}
POST https://api.sixtyfour.ai/search/query
```

Send the `search_id` on the first request, then send only `{"cursor": "..."}` for each next page:

```json theme={null}
{
  "search_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
  "page_size": 100,
  "max_results": 5000
}
```

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

| Parameter | Type | Default | Description |
| - | - | - | - |
| `search_id` | string | — | A completed deep, filter, or lookalike search. See [Replay a saved search](#replay-a-saved-search). |
| `page_size` | integer | `10` | Rows per page, from 1 to 100. |
| `max_results` | integer | `1000` for API keys | Total rows across all pages, up to your [ceiling](#search-limits). |
| `output_shape` | string | `raw` | `raw` returns each record as stored. `workflow` returns the flattened rows used by workflows and [CSV exports](#csv-columns). |
| `exclude_entity_ids`, `exclude_list_ids` | string\[] | — | People or companies to leave out. See [Search Exclusions](https://docs.sixtyfour.ai/api-reference/search/search-exclusions.md). |
| `cursor` | string | — | `next_cursor` from the previous page. Send it alone. See [Paginate with cursors](#paginate-with-cursors). |

`POST /search/query` also runs new searches from filters or a natural language query. For those inputs, see [Filter Search → Search query](https://docs.sixtyfour.ai/api-reference/search/filter-search.md#search-query).

### Replay a saved search

A request with `search_id` runs the saved search's query again and starts a new set of pages under the same `search_id`.

A replay carries:

* The saved query and its mode.
* For a lookalike search, its ranking and its seed people, who stay excluded from results.

A replay does not carry:

* `max_results`. It defaults to 1,000 for API keys, whatever the original search used. Send it again.
* `page_size`, `output_shape`, and exclusions. Send them on the first request.

The saved search must be `completed`. Replaying a search that is still running, or one that failed, returns an error. A replay does not save a new search.

### Paginate with cursors

Each page returns `has_more` and, while more rows remain, a `next_cursor`. Send `{"cursor": "..."}` alone to get the next page. Mode, page size, output shape, and exclusions carry over.

* Stop when `has_more` is `false`. The last page omits `next_cursor` and `cursor_expires_in_seconds`; they are not returned as `null`.
* A page can hold fewer rows than `page_size`, even zero, while `has_more` is `true`. Keep paging.
* Each cursor works once. Sending a cursor again after its page was served fails with `Cursor expired. Start a new search.`
* Send each cursor only after the previous response arrives. While a request with a cursor is still running, a second request with the same cursor returns `409`.
* A cursor expires `cursor_expires_in_seconds` after it is issued (30 minutes). It stops working sooner if an exclusion list it applies is deleted, and it can expire early in other cases. When a cursor fails, start again from the `search_id`.
* If a cursor request returns `429` or `503`, wait for `Retry-After` and send the same cursor again. If that fails with `Cursor expired`, start again from the `search_id`.
* A cursor request accepts only `cursor`. Sending a query, paging, or exclusion field with it fails the request. `mode`, `persist_history`, and the location-expansion fields are ignored, as are fields sent as `null` or `false`.
* A cursor works only for the organization that created it.

### Query response

| Field | Description |
| - | - |
| `search_id` | Saved search ID. Reuse it with `POST /search/query` or `POST /search/export`. |
| `results` | Rows for this page, in the selected `output_shape`. A `raw` row holds `raw_document_id`, `raw_source` (the record), and `raw_score` (its relevance score, or `null`). Lookalike rows can add [ranking fields](https://docs.sixtyfour.ai/api-reference/search/lookalike-search.md#page-ranked-results). |
| `has_more` | `true` while another page is available. |
| `next_cursor`, `cursor_expires_in_seconds` | Token for the next page and its lifetime in seconds. Omitted on the last page. |
| `page_size`, `page_count`, `page_number`, `total_pages`, `max_pages` | Pagination details. `page_count` is the number of rows on this page. `total_pages` is omitted while `has_more` is `true`. |
| `total_results` | Rows returned so far. |
| `total_available` | Exact total matches. Omitted when unknown, including when exclusions apply. |
| `total_available_lower_bound` | Minimum total matches when counting stopped at 50,000. |
| `max_results`, `remaining_results` | The effective cap and the rows still available under it. |
| `charge_credits` | Credits charged for this page. |
| `parsed_query`, `natural_language_query` | The structured query, round-trippable into a new request, and the original `query` text. |
| `output_shape` | Shape of `results`. |
| `request_duration_ms` | Server-side duration. |
| `unmet_constraints`, `company_filter_truncated` | Present when results do not cover the whole query. See [Fields that change what results mean](#fields-that-change-what-results-mean). |
| `engine`, `plan`, `cohort_size`, `ranking_policy`, `ranking_strategy` | Diagnostics returned on some accounts. |
| `resource_handle_id`, `exported_count` | Legacy fields, always `""` and `0`. |

Fields without a value are omitted from the response, not returned as `null`. Inside `results`, rows keep their `null` values.

### Fields that change what results mean

Check these fields on every page. When present, the results do not cover everything the query asked for.

| Field | Meaning |
| - | - |
| `unmet_constraints` | Parts of the query the search did not apply, as a list of field names. Either your account's search has no such field, or the search dropped the constraint because the full query matched too few rows. Results can include rows outside these constraints. |
| `company_filter_truncated.total_matched` | The company filters of a people search matched more than 10,000 companies. Results cover people at the 10,000 largest matching companies only. |
| `company_filter_truncated.excluded_total_matched` | A company exclusion matched more than 10,000 companies. Only the 10,000 largest are excluded, so people at smaller excluded companies can appear. |

Results and fields differ by account, and these fields appear only on some accounts. [`GET /search/filter-capabilities`](https://docs.sixtyfour.ai/api-reference/search/filter-search#filter-capabilities) lists the fields your account can filter on.

### Example: page through a search

```bash cURL theme={null}
API_KEY="YOUR_API_KEY"
BASE_URL="https://api.sixtyfour.ai"
BODY='{"search_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924", "page_size": 100, "max_results": 5000}'
RETRIES=0

while true; do
  HTTP_CODE=$(curl -sS -o page.json -D headers.txt -w '%{http_code}' \
    -X POST "$BASE_URL/search/query" \
    -H "x-api-key: $API_KEY" \
    -H "Content-Type: application/json" \
    -d "$BODY")

  if [ "$HTTP_CODE" = "429" ] || [ "$HTTP_CODE" = "503" ]; then
    RETRIES=$((RETRIES + 1))
    [ "$RETRIES" -le 5 ] || { echo "Search query kept returning $HTTP_CODE"; exit 1; }
    RETRY_AFTER=$(awk 'tolower($1) == "retry-after:" {print $2}' headers.txt | tr -d '\r')
    sleep "${RETRY_AFTER:-10}"
    continue
  fi
  if [ "$HTTP_CODE" != "200" ]; then
    cat page.json
    exit 1
  fi
  RETRIES=0

  jq -c '.results[]' page.json >> rows.jsonl
  [ "$(jq -r '.has_more' page.json)" = "true" ] || break
  BODY=$(jq -c '{cursor: .next_cursor}' page.json)
done
```

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

import requests

BASE_URL = "https://api.sixtyfour.ai"
HEADERS = {"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}


def post_query(body):
    for _ in range(5):
        response = requests.post(
            f"{BASE_URL}/search/query", headers=HEADERS, json=body, timeout=120
        )
        if response.status_code in (429, 503):
            time.sleep(int(response.headers.get("Retry-After", "10")))
            continue
        response.raise_for_status()
        return response.json()
    raise RuntimeError("Search query kept returning 429 or 503")


rows = []
page = post_query({
    "search_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
    "page_size": 100,
    "max_results": 5000,
})
while True:
    rows.extend(page["results"])
    if page.get("unmet_constraints"):
        print("Not applied:", page["unmet_constraints"])
    if not page["has_more"]:
        break
    page = post_query({"cursor": page["next_cursor"]})

print(f"Fetched {len(rows)} rows")
```

```javascript JavaScript theme={null}
const BASE_URL = "https://api.sixtyfour.ai";
const headers = { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function postQuery(body) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const response = await fetch(`${BASE_URL}/search/query`, {
      method: "POST",
      headers,
      body: JSON.stringify(body),
      signal: AbortSignal.timeout(120_000),
    });
    if (response.status === 429 || response.status === 503) {
      await sleep(Number(response.headers.get("Retry-After") ?? 10) * 1000);
      continue;
    }
    if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
    return response.json();
  }
  throw new Error("Search query kept returning 429 or 503");
}

const rows = [];
let page = await postQuery({
  search_id: "e3b0c442-98fc-1c14-9afb-f4c8996fb924",
  page_size: 100,
  max_results: 5000,
});
while (true) {
  rows.push(...page.results);
  if (page.unmet_constraints) console.warn("Not applied:", page.unmet_constraints);
  if (!page.has_more) break;
  page = await postQuery({ cursor: page.next_cursor });
}

console.log(`Fetched ${rows.length} rows`);
```

***

## Export search results

Generate a CSV from any completed search: deep, filter, or lookalike.

```http theme={null}
POST https://api.sixtyfour.ai/search/export
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `search_id` | string | — | The search to export. It must be `completed`. |
| `parsed_query` | object | — | A structured query from a previous response, as an alternative to `search_id`. |
| `mode` | string | `company` | `people` or `company`. Taken from the saved search when you send `search_id`. Send it with `parsed_query`: a people query sent without `mode` runs as a company search. |
| `max_results` | integer | `1000` | Rows to export, up to your [ceiling](#search-limits). |
| `exclude_entity_ids`, `exclude_list_ids` | string\[] | — | Exclusions to apply to the CSV. A `search_id` does not carry exclusions from earlier requests. |

An export runs the saved query again, as a [replay](#replay-a-saved-search) does, so its rows can differ from the CSV a deep search wrote.

```json theme={null}
{
  "task_id": "search_export_e3b0c442-98fc-1c14-9afb-f4c8996fb924",
  "status": "running",
  "message": "Export started. Poll /search/status/{task_id} for progress."
}
```

1. Call `POST /search/export` and save the `task_id`.
2. Poll [`GET /search/status/{task_id}`](#get-search-status) until it is `completed`, then read `resource_handle_id`.
3. Download the CSV with [`GET /search/download`](#download-search-results).

One export per `search_id` runs at a time. While one runs, another export request for the same search fails; poll `search_export_{search_id}` instead. An export with `parsed_query` gets a new ID each time.

### CSV columns

A CSV has one row per result, with the same columns as `output_shape: "workflow"` rows.

| Mode | Columns |
| - | - |
| `people` | `linkedinUrl`, `fullName`, `headline`, `summary`, `logoUrl`, `currentCompany`, `currentPosition`, `currentCompanyLogo`, `currentCompanyStartDate`, `currentPositionStartDate`, `previousCompany`, `previousPosition`, `locationName`, `locationCountry`, `industry`, `totalExperienceYears`, `education`, `degree`, `fieldOfStudy`, `graduationYear`, `skills`, `certifications`, `languages`, `connectionsCount`, `followersCount` |
| `company` | `linkedinUrl`, `name`, `industry`, `overview`, `tagline`, `website`, `domain`, `logoUrl`, `employeeCount`, `employeesOnLinkedIn`, `revenue`, `foundedAt`, `headquartersCity`, `headquartersCountry`, `headquartersRegion`, `locationCount`, `specialties`, `tags`, `fundingAmount`, `fundingType`, `fundingYear`, `fundingRoundsCount` |

On accounts where lookalike results carry ranking fields, lookalike CSVs add `similarityScore` (0–100), `similarityRank`, and `similaritySignals`.

***

## Download search results

Get a signed URL for the CSV of a completed CSV deep search or export.

```http theme={null}
GET https://api.sixtyfour.ai/search/download?resource_handle_id={resource_handle_id}
```

The response contains `url` plus the storage `bucket` and `path` of the file.

> [!NOTE]
>
> Fetch the returned `url` with your `x-api-key` header; the signed token does not replace authentication. Disable redirects when sending your API key.

> [!WARNING]
>
> Signed URLs expire after 15 minutes. Download the file right away or request a new URL.

### Spreadsheet-safe CSV

By default, the file is escaped so that opening it in a spreadsheet never runs a formula:

* Every field is quoted.
* A cell that starts with `=`, `+`, `-`, `@`, a tab, or a line break (or a full-width `＝`, `＋`, `－`, or `＠`) gets a leading `'`. A phone number `+14155550123` arrives as `'+14155550123`. Negative numbers such as `-12.5` are left as they are.
* The response carries the header `X-Spreadsheet-Safe: 1`.

To read the file as data, append `?spreadsheet_safe=false` to the `url`. The file then arrives exactly as stored, without the header. A file that cannot be escaped, because it is not valid CSV or is too large to escape, is refused by default and downloads with `?spreadsheet_safe=false`.

```bash cURL theme={null}
# DOWNLOAD_URL is the url from GET /search/download
curl -sS "DOWNLOAD_URL?spreadsheet_safe=false" \
  -H "x-api-key: YOUR_API_KEY" \
  -o search_results.csv
```

```python Python theme={null}
csv_response = requests.get(
    download_url,
    headers={"x-api-key": "YOUR_API_KEY"},
    params={"spreadsheet_safe": "false"},
    allow_redirects=False,
)
```

```javascript JavaScript theme={null}
const csvResponse = await fetch(`${downloadUrl}?spreadsheet_safe=false`, {
  headers: { "x-api-key": "YOUR_API_KEY" },
  redirect: "error",
});
```

***

## Search limits

Return the maximum number of results your credential can request, retrieve, or export in one search.

```http theme={null}
GET https://api.sixtyfour.ai/search/limits
```

```json theme={null}
{
  "max_results": 5000
}
```

API keys get 5,000 unless your organization has a custom ceiling. The ceiling applies to `max_results` on `POST /search/start-deep-search`, `POST /search/query`, and `POST /search/export`; a higher value fails the request.

## Rate limits

For per-endpoint limits, see [Rate Limits](https://docs.sixtyfour.ai/api-reference/rate-limits.md#search). File downloads from the signed `url` share one budget with other file downloads; see [Limits by endpoint](https://docs.sixtyfour.ai/api-reference/rate-limits.md#limits-by-endpoint).

## Pricing

See [Credits & Pricing Guide](https://docs.sixtyfour.ai/guides/credits-and-pricing.md) for more information.

## Errors

For error responses (400, 403, 404, 409, 429, etc.), see [Handling Errors](https://docs.sixtyfour.ai/api-reference/errors.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.