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

# Build a list from search

> Turn a prospect search into a saved, enriched list: check filter values, preview cheaply, save once, and poll until the rows land.

This guide walks the full loop from an idea ("US fintech CFOs") to a populated list you can [enrich](/guides/custom-columns), [export](/guides/export-a-list), or [push into a campaign](/guides/campaigns). The flow is deliberately split into a cheap preview step and a billed save step, so you tune filters before you commit credits.

<Info>
  You need an API key with the `search` scope for every endpoint on this page, plus the `lists` scope for the final polling step — see [authentication](/authentication) and [scopes](/concepts/scopes). Searching and saving both debit search credits; see [credits](/concepts/credits).
</Info>

<Steps>
  <Step title="Look up the accepted filter values">
    Enumerated filters (industries, seniority levels, locations, company sizes, and so on) match **exact strings from a fixed vocabulary**. A misspelled or paraphrased value does not return an error — it simply matches nothing, and your search comes back empty or oddly small. So before you write filters by hand, fetch the vocabulary:

    ```bash theme={null}
    curl https://api.tryfuse.ai/api/v1/business/prospects/filter-options \
      -u "af_YOUR_KEY:"
    ```

    ```json theme={null}
    {
      "filterOptions": {
        "job_title_levels": ["c_suite", "vp", "director", "manager", "senior", "entry"],
        "industries": ["financial services", "computer software"]
      }
    }
    ```

    Copy values verbatim, including casing. If a search that should obviously match returns nothing, a value that is not in `filterOptions` is the first thing to check.
  </Step>

  <Step title="Preview the search">
    `POST /business/prospects/search` runs the search and returns one page of matching profiles. Enumerated filters take an array of `{ "status": "include" | "exclude", "value": "..." }` objects — `exclude` flips a value into an exclusion. `size` controls the page size (1–100, default 10).

    ```bash theme={null}
    curl -X POST https://api.tryfuse.ai/api/v1/business/prospects/search \
      -u "af_YOUR_KEY:" \
      -H "Content-Type: application/json" \
      -d '{
        "filters": {
          "job_title_levels": [{ "status": "include", "value": "c_suite" }],
          "industry": [{ "status": "include", "value": "financial services" }],
          "location_country": [{ "status": "include", "value": "united states" }]
        },
        "size": 10
      }'
    ```

    ```json theme={null}
    {
      "profiles": [
        {
          "firstName": "Ada",
          "lastName": "Nwosu",
          "jobTitle": "Chief Financial Officer",
          "companyName": "Brightpay",
          "linkedinUrl": "https://www.linkedin.com/in/ada-nwosu"
        }
      ],
      "scrollToken": "eyJwYWdlIjoyfQ"
    }
    ```

    Profiles carry identity fields only — `firstName`, `lastName`, `jobTitle`, `companyName`, `linkedinUrl`. Emails, phones, and the rest arrive when you save to a list and enrichment runs.

    To fetch the next page, repeat the request with the same `filters` and the `scrollToken` from the previous response in the body. See [pagination](/concepts/pagination) for how scroll tokens behave.

    Each page you fetch debits search credits, so keep `size` small while you tune filters. Previewing is the cheap way to tune filters before you commit to saving.

    <Accordion title="Filter shapes and the full key list">
      The `filters` object accepts three shapes of field, plus a `rangeInputs` object for numeric ranges:

      | Shape                 | How to pass it                                                                      | Filter keys                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
      | --------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | Include/exclude array | `[{ "status": "include", "value": "..." }]`; values must come from `filter-options` | `industry`, `job_title_levels`, `job_title_role`, `job_title_sub_role`, `job_company_size`, `job_company_inferred_revenue`, `job_company_industry`, `job_company_location_country`, `job_company_location_region`, `job_company_location_continent`, `location_continent`, `location_country`, `location_region`, `languages.name`, `education.degrees`, `experience.company.type`, `experience.title.levels`, `experience.title.role`, `experience.title.sub_role`, `experience.company.location.country`, `experience.company.location.continent`, `experience.company.industry` |
      | String or array       | A bare string, or an array mixing strings and include/exclude objects               | `job_company_name`, `job_company_website`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
      | Free text             | A string or an array of strings (`keywords` is array-only)                          | `full_name`, `first_name`, `last_name`, `sex`, `work_email`, `mobile_phone`, `personal_emails`, `job_title`, `job_company_location_name`, `location_name`, `linkedin_url`, `github_url`, `github_username`, `facebook_url`, `education.school.name`, `experience.title.name`, `experience.company.name`, `experience.company.website`, `experience.company.location.name`, `job_start_date`, `skills`, `certifications.name`, `keywords`                                                                                                                                           |

      `rangeInputs` sits alongside the other filters and takes `{ "min": number, "max": number }` objects for `job_company_employee_count`, `inferred_years_experience`, and `job_company_total_funding_raised`:

      ```json theme={null}
      {
        "filters": {
          "rangeInputs": {
            "job_company_employee_count": { "min": 50, "max": 500 }
          }
        }
      }
      ```
    </Accordion>
  </Step>

  <Step title="Save the results into a list">
    When the preview looks right, save the matches with `POST /business/prospects/search/save-to-list`. Send the same `filters`, a `limit` for how many rows to save (1–500, required), and a destination: `listName` to create a new list, or `listId` to add to an existing one.

    ```bash theme={null}
    curl -X POST https://api.tryfuse.ai/api/v1/business/prospects/search/save-to-list \
      -u "af_YOUR_KEY:" \
      -H "Content-Type: application/json" \
      -d '{
        "filters": {
          "job_title_levels": [{ "status": "include", "value": "c_suite" }],
          "industry": [{ "status": "include", "value": "financial services" }],
          "location_country": [{ "status": "include", "value": "united states" }]
        },
        "listName": "US fintech CFOs",
        "limit": 100
      }'
    ```

    ```json theme={null}
    { "listIds": ["68a1f20b9c41d20014b3e901"] }
    ```

    The response is a `202`: the job has started, not finished. The search runs server-side, and contacts appear in the list as enrichment completes.

    Note that preview and save use different sizing fields on purpose: `size` is a page size for browsing results, while `limit` is the total number of rows the save job will pull and enrich.

    <Warning>
      This endpoint runs its own search — it does not reuse results from a previous `POST /prospects/search`. Passing the same filters to both endpoints pays for the search twice. Preview with a small `size` to tune filters, then save once. If cost matters most, skip the preview entirely and call save-to-list directly. Hand-picking a subset of previewed profiles to save is not supported; narrow `filters` instead. See [credits](/concepts/credits).
    </Warning>
  </Step>

  <Step title="Poll the list until it fills">
    Read the list's rows and watch `pagination.totalRecords`. The save job is done when the count stops growing between polls.

    ```bash theme={null}
    curl "https://api.tryfuse.ai/api/v1/business/lists/68a1f20b9c41d20014b3e901/rows?limit=100&pageNum=1" \
      -u "af_YOUR_KEY:"
    ```

    ```json theme={null}
    {
      "data": [
        {
          "id": "68a1f2c89c41d20014b3e955",
          "firstName": "Ada",
          "lastName": "Nwosu",
          "linkedinUrl": "https://www.linkedin.com/in/ada-nwosu",
          "jobTitle": "Chief Financial Officer",
          "companyName": "Brightpay",
          "companyDomain": "brightpay.com",
          "columnValues": {
            "68a1f3009c41d20014b3e9a1": "warm"
          }
        }
      ],
      "pagination": {
        "pageNum": 1,
        "totalPages": 5,
        "totalRecords": 412
      }
    }
    ```

    This endpoint uses page-number pagination (`pageNum` and `limit`, max 1000 per page) rather than scroll tokens — see [pagination](/concepts/pagination). A wrong or inaccessible `listId` returns a `404` with the standard [error envelope](/concepts/errors):

    ```json theme={null}
    {
      "error": {
        "code": "LIST_NOT_FOUND",
        "message": "The requested list was not found.",
        "param": null,
        "doc_url": "https://fuseai.com/api/errors/list_not_found",
        "request_id": "req_8fK2mQ"
      }
    }
    ```
  </Step>
</Steps>

## Replay a saved search

Saved searches let you rerun a query you have already tuned, without carrying the filter JSON around. `GET /business/saved-searches` lists the searches saved on your account, whether they were saved from the Fuse app or the API:

```bash theme={null}
curl https://api.tryfuse.ai/api/v1/business/saved-searches \
  -u "af_YOUR_KEY:"
```

```json theme={null}
{
  "savedSearches": [
    {
      "id": "68a1f7409c41d20014b3ed31",
      "title": "US fintech CFOs",
      "createdAt": "2026-06-14T08:30:00.000Z"
    }
  ]
}
```

Replay one with `POST /business/saved-searches/{savedSearchId}/run`. The response looks like a normal search page, plus one extra field:

```bash theme={null}
curl -X POST https://api.tryfuse.ai/api/v1/business/saved-searches/68a1f7409c41d20014b3ed31/run \
  -u "af_YOUR_KEY:"
```

```json theme={null}
{
  "profiles": [
    {
      "firstName": "Ada",
      "lastName": "Nwosu",
      "jobTitle": "Chief Financial Officer",
      "companyName": "Brightpay",
      "linkedinUrl": "https://www.linkedin.com/in/ada-nwosu"
    }
  ],
  "scrollToken": "eyJwYWdlIjoyfQ",
  "ignoredFilterKeys": []
}
```

`ignoredFilterKeys` lists any stored filter keys that could not be translated into search fields. This happens with stale saved searches — ones saved against filter names that have since changed. Ignored filters are dropped, not approximated, so a non-empty array means the replay ran with **fewer constraints than the original search** and the results are broader than what you saved.

<Note>
  Check `ignoredFilterKeys` before acting on replayed results. If it is non-empty, recreate the search with current filter keys (start from [step 1](#look-up-the-accepted-filter-values)) rather than saving a broader-than-intended result set to a list.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Add custom columns" icon="table-columns" href="/guides/custom-columns">
    Attach your own fields to the list and fill them per contact.
  </Card>

  <Card title="Export the list" icon="file-arrow-down" href="/guides/export-a-list">
    Start an export job, poll it, and download the CSV.
  </Card>

  <Card title="Launch a campaign" icon="paper-plane" href="/guides/campaigns">
    Turn the list into a sequenced outreach campaign.
  </Card>

  <Card title="Upload your own contacts" icon="upload" href="/guides/upload-your-contacts">
    Bring existing contacts into a list instead of searching.
  </Card>
</CardGroup>
