Skip to content
SearchLeads/DevelopersGet API key
Documentation v1
DEVELOPER DOCUMENTATIONREST API · v1

Search API

Search people and companies, read results, and paginate safely.

POST/functions/v1/people-search

Endpoint

POST https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/people-search

Despite its name, this endpoint accepts both "people" and "company" in searchType. Company search is controlled by account configuration. Check capabilities for enabled types.

Request body

FieldTypePurpose
searchTypestringpeople or company; defaults to people.
filtersobjectStructured inclusion filters: arrays, enums, ranges, and metrics.
excludedFiltersobjectValues to remove, using the same field keys as filters.
textFiltersobjectPlain string text values such as contact.keyword.
filterRulesobjectOptional matching rules, including match-all and search mode.
locationIdentitiesobjectStructured location identities keyed by filter field and selected label, then kind, city, state, and country. Send the same identities through REST and MCP to distinguish cities from states.
pagenumberZero-indexed page; defaults to 0.
sizenumberResults per page; permitted size comes from capabilities.
sortBy, sortOrderstringSort field and asc or desc.
search_afterarrayCursor from the prior response for deep pagination.
list_filtersarrayInclude or exclude lists by owned list ID.
listIdstringLegacy alias for one included application list.
record_summaryobjectSaved-record scope with enabled, savedListIds, and matchMode (any or all).
savedOnly, netNewOnlybooleanLimit to saved or net-new records; mutually exclusive.

lookalikeDomains applies to company search only. To find people at known domains, use account.domain.

Included list_filters are ANDed; excluded lists are removed. List ownership and active lookup versions are checked by the server. record_summary affects results only with savedOnly or netNewOnly; omit it to use the account-wide saved set. With matchMode: "any", net-new excludes the union of selected lists; with "all", it excludes records in every selected list. Unrelated unknown extension fields remain ignored and appear in meta.requestWarnings. Known filter-bearing typos such as filter instead of filters, malformed supplied containers, string booleans, invalid sorts, and malformed cursors return 400 before billable work. REST uses searchType; MCP uses search_type. Filter access and allowed values are enforced by the authenticated capability contract.

All, Saved only, or Exclude saved

These are account-owned record scopes, separate from ordinary inclusion/exclusion filters. They work for both searchType: "people" and searchType: "company". People searches use Saved Contacts; Company searches use Saved Companies. Saving a person does not save or exclude their employer company.

ScopeREST search bodyMCP search_prospects arguments
All matching recordsOmit both scope flags, or set both to falseOmit both scope flags, or set both to false
Exclude saved / Net New"netNewOnly": true"net_new_only": true
Saved only"savedOnly": true"saved_only": true

Never set both flags to true. Use JSON booleans, not strings. Omit record_summary to use all saved records for the authenticated account and matching search type. A selected-list summary changes which saved set is used; it does not replace the scope flag.

Read authenticated capabilities search.recordScopesBySearchType for enabled scopes and search.recordScopeParameters for exact REST/MCP field names. An empty scope list means that search type is unavailable. People Saved/Net New scopes may be disabled by account configuration; handle the returned error rather than retrying without the restriction.

For example, find new companies in the United States:

{
  "searchType": "company",
  "filters": { "account.location": ["United States"] },
  "netNewOnly": true,
  "size": 3
}

Use "searchType": "people" with the same flag to exclude saved contacts. To search only saved records, replace netNewOnly with savedOnly. Pass the returned search_id to export creation: its saved/net-new scope is retained. Searching alone never adds records to Saved; choose saveContacts when creating the export. See saving exported records.

Matching follows the UI

Rules apply to values within one filter, rather than globally to the entire search. Normal multi-select filters default to ANY: selecting United States and United Kingdom accepts either country. ALL requires both selections to match the record's indexed values and may return no results for a single-valued field. Distinct filter fields still constrain the same search together.

Omit a rule to inherit that filter's current UI/catalog default. Some configured filters default to ALL; read matching.defaultCondition and ruleSchema in capabilities. Explicit false overrides an ALL default and survives export/history reruns. Where the UI supports this choice, send:

{
  "searchType": "company",
  "filters": { "account.location": ["United States", "United Kingdom"] },
  "filterRules": { "account.location": { "matchAll": false } },
  "size": 3
}

Use boolean true for ALL or boolean false for ANY. The text "true" is invalid. The compatibility alias condition accepts "any" or "all"; omit it when using matchAll, or ensure both agree. Supported text modes are SMART, WORD, and STRICT; source arrays and metric row shapes are documented per field. Unknown nested rule properties are rejected rather than silently ignored.

Explicit sortBy accepts name, title, contact.fullName, contact.experience.latest.title, organization.name.raw, organization.employeesOnLinkedin, organization.foundedYear, organization.revenueMin, or organization.revenueMax. Use only fields meaningful to the chosen index. sortOrder accepts asc or desc. The response cursor has two elements (the selected sort value and _shard_doc); send it unchanged with the same query, scope, size, and sort. An invalid cursor returns an error instead of restarting the first page. Do not send conflicting camelCase/snake_case aliases.

Example

curl -X POST "https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/people-search" \
  -H "Content-Type: application/json" \
  -H "x-searchleads-api-key: sl_live_your_full_api_key_here" \
  -d '{
    "searchType": "people",
    "filters": {
      "account.industry": ["Software Development"],
      "account.employeeSize": { "min": 50, "max": 500 },
      "contact.seniority": ["C-Team"]
    },
    "excludedFilters": { "account.location": ["India"] },
    "textFilters": { "contact.keyword": "outbound sales" },
    "page": 0,
    "size": 25
  }'

Use filter discovery to verify available fields and readable values for your account before sending them.

meta.chargedCredits and meta.chargedCreditKind identify the actual debit and credit pool. API People responses also quote meta.apiProspectCostPerResult; Company responses quote meta.companySearchCostPerResult. These search charges round the returned-row charge up to a whole credit; search pricing is separate from fractional CSV export pricing. A zero-row response has no returned-row charge. Read the actual response and account policy rather than assuming an example price.

C-Team is a current seniority value; Founder is not. To target founders specifically, send "contact.experience.latest.title": ["Founder"] in filters. Current job-title text accepts custom values without a catalog ID unless your account contract explicitly restricts that field.

Matching rules

Key filterRules by the same field used in the request. Values within a filter are normally ORed; separate fields are ANDed. matchAll: true or condition: "all" requires all values within that field. An exclusion bucket removes matching records; exclude: true on a field rule also excludes that field's values.

Text matching supports SMART (default), WORD, and STRICT. These modes control text-query matching; they do not turn Contact Name into an exact identity lookup. The contract's ruleSchema lists relevant rules and keyword-source enums for each field.

{
  "searchType": "people",
  "textFilters": { "account.keyword": "software" },
  "filterRules": {
    "account.keyword": { "mode": "SMART", "sources": ["NAME", "DESCRIPTION"] }
  },
  "size": 1
}

The rule shape is { "matchAll": boolean, "condition": "any" | "all", "exclude": boolean, "mode": "SMART" | "WORD" | "STRICT" }. Keyword fields additionally accept sources, includeSources, and excludeSources as arrays of exact source IDs in ruleSchema. Avoid source IDs from a different field.

Metric filters accept rows such as [{ "function": "DIMENSION_FROM_LOOKUP", "min": 1, "max": 10 }]; select the dimension with filter-options first. Headcount Growth is a direct percentage range, not a department metric. Legacy metricRows rules can carry functions: string[], start, end, timeFrame, and exclude; new clients should use explicit structured rows and the live value contract.

Location identities

People Search's account.location means company headquarters, while contact.location means the person's location. Standalone Company Search retains its own location contract. A contact outside the US can correctly match a US headquarters filter.

Disambiguate city, state, and country names using locationIdentities. Key it by the exact filter field, then by the selected label. This selects the US state of Georgia, rather than the country:

{
  "searchType": "people",
  "filters": { "contact.location": ["Georgia"] },
  "locationIdentities": {
    "contact.location": {
      "Georgia": { "kind": "state", "state": "Georgia", "country": "United States" }
    }
  },
  "size": 1
}

Use kind: "city" with city, state, and country, or kind: "country" with country for the other identity types. Preserve these identities in REST and MCP requests.

Response and pagination

{
  "results": {
    "totalElements": 18432,
    "recordCounts": {
      "totalRecords": 18432,
      "savedRecords": 213,
      "netNewRecords": 18219,
      "savedRecordScope": "query"
    },
    "content": [{
      "id": "abc123",
      "firstName": "Sarah",
      "lastName": "Chen",
      "name": "Sarah Chen",
      "title": "CEO",
      "linkedinUrl": "https://linkedin.com/in/sarahchen",
      "organization": { "name": "Example Company", "industry": "Business Services", "employeeCount": 120 }
    }],
    "results": ["Deprecated version 1 duplicate of content"]
  },
  "search_id": "9e9013ff-b8af-4f85-b4dc-3e0c995da0c1",
  "search_id_expires_at": "2026-04-22T09:15:00.000Z",
  "rate_limits": {
    "per_minute": { "limit": 60, "remaining": 59 },
    "per_day": { "limit": null, "current": null, "remaining": null, "unlimited": true }
  },
  "meta": { "responseVersion": 1, "page": 0, "size": 25, "searchId": "9e9013ff-b8af-4f85-b4dc-3e0c995da0c1" }
}

Start with page: 0, increment the page until page * size >= totalElements, and use search_after for deep pagination beyond the Elasticsearch 10,000-result offset window. Each page counts toward rate limits. People search billing is based on returned prospects; it does not create an export job. Keep the returned search_id if you will create an export.

The response returns the next cursor as top-level next_search_after. Send that array unchanged as search_after, keep the same filters, scope, size, and sort, and omit an advancing page. Stop when the cursor is null or content is empty. Treat cursors as opaque arrays; do not construct them from a displayed record ID.

{
  "searchType": "people",
  "filters": { "account.domain": ["example.com"] },
  "size": 25,
  "search_after": [12.5, 47263732265]
}

These numbers illustrate the shape; use the real prior response's next_search_after. Check meta.appliedFilterFields and meta.skippedFilters before accepting an audience: HTTP 200 alone does not prove every condition was applied. Validation failures can return top-level skipped_filters.

The default response version is 1. The capabilities response lists supported versions and the X-SearchLeads-Response-Version header used to request another version; version 2's result path is results.content.

JavaScript

const response = await fetch("https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/people-search", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-searchleads-api-key": process.env.SEARCHLEADS_API_KEY,
  },
  body: JSON.stringify({
    searchType: "people",
    filters: { "account.industry": ["Software Development"] },
    textFilters: { "contact.keyword": "developer tools" },
    excludedFilters: { "account.location": ["India"] },
    page: 0,
    size: 25,
  }),
});
if (response.status === 429) throw new Error(`Retry after ${response.headers.get("Retry-After")} seconds`);
if (!response.ok) throw new Error(`Search failed: ${response.status}`);
const data = await response.json();
console.log(data.results.totalElements, data.results.content);

Python

import os
import time
import requests

url = "https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/people-search"
body = {
    "searchType": "people",
    "filters": {"account.industry": ["Software Development"]},
    "page": 0,
    "size": 25,
}
headers = {"x-searchleads-api-key": os.environ["SEARCHLEADS_API_KEY"]}
while True:
    response = requests.post(url, json=body, headers=headers)
    if response.status_code != 429:
        response.raise_for_status()
        break
    time.sleep(int(response.headers.get("Retry-After", "60")))
data = response.json()
print(data["results"]["totalElements"])

For matching results across UI, REST, and MCP, use the same environment, account, dataset, record scope, filters (including location identities), and sort. The UI preserves server ranking by default; clicking a column sorts the loaded preview only.

Company location in results

Company Location can match a company's primary address or headquarters. For supported included location filters, Company results add searchLocation with city, state, country, source, and matched. The Company UI and geographic CSV columns use this same address tuple. Original source addresses remain available in the API response.

matched: true identifies an address that satisfied a complete selected location alternative. If multiple addresses match, the primary address is preferred. With ALL, different selected locations may be satisfied by different addresses; one displayed tuple cannot show every matching address. Historical structured queries can also match components across different addresses. When no single address satisfies a complete alternative, matched: false identifies one coherent fallback address, with missing components left blank. Exclusion-only searches and unsupported historical query shapes retain their existing presentation.

Legal Structure catalog values can contain serialized arrays representing combined structures. The UI displays these readably, but requests must still send the exact option ID or original label returned by filter-options. Do not split a combined catalog entry into invented independent values.

API and MCP prospect searches are billed according to the account's current pricing and can debit the export-credit pool. Read meta.chargedCredits and meta.chargedCreditKind to identify the charge and pool; the name chargedSearchCredits does not by itself identify a separate wallet. Export-file charges are accounted for separately. Repeating a search to inspect records can therefore incur its own search charge.

Experience Duration (contact.experience.latest.duration) is expressed in months relative to the date a new search runs. Re-running the same 0–84 month filter on a later day moves its underlying job-start-date window accordingly. Exporting an existing search_id uses its pinned execution snapshot instead of recalculating that window.

On this page