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 and scopes. Searching and saving both debit search credits; see credits.1
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: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.Free-text fields (locations, titles, skills, schools, company names, websites) have no fixed list to enumerate — for those, GET /business/prospects/autocomplete suggests fully-qualified values from the same vocabulary the Fuse app’s type-ahead uses:field is one of location, region, title, skill, school, company, website. A partial string like "San Fran" matches nothing in a search; the suggestion’s fully-qualified form does. region suggestions additionally carry a countries array (likeliest first), because the same region name can exist in several countries — pair the region with a location_country filter to disambiguate, like the app does.2
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).first_name, job_title, job_company_name, linkedin_url, and so on), with string values normalized to lowercase, plus savedProfile/savedBy telling you whether someone on your team already saved that person. Emails and phones are never present here — they arrive when you save to a list and enrichment runs.total is the true match count for the whole query (not the page size), and filters echoes the filter set that actually ran. With literal filters the echo is what you sent; with the alternative sources below it is what your input resolved to — inspect it, refine it, and send it back as filters on the next call or on save-to-list.Three ways to express the search
Three ways to express the search
The body carries exactly one filter source; combining two returns a A
422.Literal filters — the structured form shown above.A saved search — savedSearchId, or savedSearchName for an exact title match. Names resolve strictly: no match is a 404, and two searches sharing the title is a 409 SAVED_SEARCH_NAME_AMBIGUOUS — the API never silently picks one. The response additionally carries ignoredFilterKeys (see Saved searches).A natural-language query — the same smart search as the app’s prompt box. The prose is converted into structured filters server-side, the search runs on those, and the response echoes them plus unresolvedCriteria: the parts of your prose that could not be mapped to any filter, quoted verbatim. A non-empty array means the results are broader than your sentence — tighten by editing the echoed filters directly.query too vague to yield any filter returns 422 QUERY_FILTERS_INVALID rather than running (and billing) an unconstrained search. Query conversion also has its own tighter rate limit on top of the search one, since it runs a model per call.id on list rows after a save, or via POST /business/contacts/resolve from a LinkedIn URL.To fetch the next page, repeat the request with the same filters and the scrollToken from the previous response in the body. See 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.Filter shapes and the full key list
Filter shapes and the full key list
The
filters object accepts three shapes of field, plus a rangeInputs object for numeric ranges: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:3
Save the results into a list
When the preview looks right, save the matches with The response is a
POST /business/prospects/search/save-to-list. The body takes the same filter source as the search — filters, savedSearchId/savedSearchName, or query (exactly one) — plus a limit for how many rows to save (1–10,000, required) and a destination: listId for an existing list, or listName, which creates the list or reuses the existing one with exactly that name (a name shared by two lists is a 409 LIST_NAME_AMBIGUOUS; use listId instead).enrich selects what runs on the saved rows:Enrichment adds its own per-row credits on top of the save; contacts that already carry the requested data are not re-charged. Saving into a list whose every contact already has the requested enrichment returns
409 ALL_CONTACTS_ALREADY_ENRICHED before anything is billed.An optional campaignId or campaignName (strict resolve — 404 on a miss, 409 CAMPAIGN_NAME_AMBIGUOUS on a shared name) attaches the destination list to that campaign in the same call.202: the job has started, not finished. The search runs server-side, and contacts appear in the list as saving (and enrichment, when requested) completes. Enriched saves also report totalContacts and estimatedMinutes; a campaign attach reports back as "campaign": { "id": "…", "attached": true }.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.Campaign-attach timing: the attach happens when the save starts. A draft campaign enrolls the full list when you approve it, but an ACTIVE non-dynamic campaign enrolls only the rows present at attach time — approve after the list fills, or use a dynamic campaign, to reach everything saved.
4
Poll the list until it fills
Read the list’s rows and watch This endpoint uses page-number pagination (
pagination.totalRecords. The save job is done when the count stops growing between polls.pageNum and limit, max 1000 per page) rather than scroll tokens — see pagination. A wrong or inaccessible listId returns a 404 with the standard error envelope:Saved searches
Saved searches let you rerun a query you have already tuned, without carrying the filter JSON around. They are shared with the Fuse app in both directions: a search saved in the app is callable from the API, and a search created through the API opens identically in the app’s filter rail.GET /business/saved-searches lists the searches saved on your account, filters reported in the same vocabulary POST /prospects/search accepts:
Create, update, delete
POST /business/saved-searches stores a filter set under a title — the same filters object the search endpoint takes:
201 returns the stored search in the list-row shape above. A filter field that cannot round-trip through the app’s filter rail is rejected with 422 SAVED_SEARCH_FILTERS_UNSUPPORTED (param names the field) rather than stored unusable — run such filters as a direct search instead.
GET /business/saved-searches/{savedSearchId} reads one. PATCH renames it and/or replaces its filters (send title, filters, or both). DELETE removes it and returns a 204:
Run one
The primary way isPOST /business/prospects/search with savedSearchId (or savedSearchName — strict resolve, 409 SAVED_SEARCH_NAME_AMBIGUOUS when two share the title), as shown in step 2. POST /business/saved-searches/{savedSearchId}/run does the same run:
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.
Check
ignoredFilterKeys before acting on replayed results. If it is non-empty, recreate the search with current filter keys (start from step 1) rather than saving a broader-than-intended result set to a list.The same loop, for companies
Every step above has a company twin under/business/companies/* (the companies scope): filter-options serves the enum vocabularies (industries, tags, NAICS codes, countries, accelerators), autocomplete resolves company names (+ orgType=investor) and office locations, POST /companies/search takes the same one-of-four filter source and echoes resolved filters + total with cursor paging, and POST /companies/search/save-to-list saves into a company list (create-or-reuse by name). There is no enrich switch on the company save — Fiber firmographics land on the saved companies automatically. Full request/response shapes are in the API reference.
Next steps
Add custom columns
Attach your own fields to the list and fill them per contact.
Export the list
Start an export job, poll it, and download the CSV.
Launch a campaign
Turn the list into a sequenced outreach campaign.
Upload your own contacts
Bring existing contacts into a list instead of searching.
Saving a company search
Company search has its own save call,POST /business/companies/search/save-to-list, with the same shape as the prospect one: pass the filters you searched with and a required limit, plus listId or listName for the destination.
202 and fills the list in the background, so follow the list’s completionStatus as in long-running work.