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

# Search people

> Search the talent graph with structured filters or recruiter-style natural language.

Search the people visible to the authenticated organization. Use `nl` for recruiter-style input, the typed filters for reproducible requests, or both. When a field is present in both forms, the explicit query parameter wins. Every response echoes the effective, saveable filter object in `query`; it does not include pagination or `view`.

The endpoint requires an API key. See [Authentication](/authentication). Search excludes profiles with data-quality flags unless `include_flagged=true` is sent.

## Natural language and output

<ParamField query="nl" type="string" placeholder="ex-Palantir FDEs, left 18mo, open-to-work">
  Optional recruiter-language query, 1-400 characters. The parser recognizes supported availability, role, company, tenure, school, skill, language, and location phrases. Use the structured `certification` or `certifications` parameter for certification matching. Unrecognized words become keyword text rather than a new filter.
</ParamField>

<ParamField query="view" type="string" default="full" placeholder="compact">
  `full` (default) returns the Person shape. `compact` returns agent-sized rows with a fixed, smaller shape. See [Person response shapes](/api-reference/people/schema).
</ParamField>

## Company and role filters

<ParamField query="company" type="string">
  Case-insensitive fragment matched against a company name or LinkedIn URL. The Gateway requires at least one character and does not impose a further length limit.
</ParamField>

<ParamField query="min_company_months" type="integer">
  Minimum tenure in months, at least `1`. With `company`, matching stints at that company are counted; without it, a person must have a qualifying stint at some company.
</ParamField>

<ParamField query="company_current" type="boolean">
  `true` requires a current position at the matching company, or at any company when `company` is omitted. `false` does not add a current-position constraint. If combined with `company_left_min_months_ago`, the left-company condition takes precedence.
</ParamField>

<ParamField query="company_left_min_months_ago" type="integer">
  Requires an integer from `1` to `600`. Requires an ended, non-current stint at the matching company, or at any company when `company` is omitted, that ended at least this many months ago. A current stint at the matching company is excluded.
</ParamField>

<ParamField query="company_active_during" type="string" placeholder="2024-06">
  A year (`YYYY`) or month (`YYYY-MM`), checked at the **first day** of that year or month, not any overlap across the whole period. Known position start/end bounds must contain that date; a missing bound is treated as open-ended. It can be used with or without `company`.
</ParamField>

<ParamField query="role_family" type="string" placeholder="forward-deployed-engineer">
  One role-family value. Kept for single-role clients; prefer `roles` when expressing more than one role or a title match.
</ParamField>

<ParamField query="min_role_months" type="integer">
  Minimum role tenure, at least `1`. Requires `role_family` or `roles`. With `company`, the legacy `role_family` form counts tenure at the matched company; otherwise it counts career-wide tenure.
</ParamField>

<ParamField query="roles" type="string" placeholder="forward-deployed-engineer:18@company,software-engineer:24">
  Comma-separated role filters, not a JSON array. Up to 5 parsed filters are used; the request value is at most 1500 characters. A family token is `family` or `family:months`; add `@company` to scope it to `company`. For title matching, use `t.contains.<encoded-title>.<months>` or `t.exact.<encoded-title>.<months>`. Multiple titles in one title token are separated by `;`. Role-family names are at most 64 characters and month values are capped at 600 by the parser. `@company` falls back to career-wide matching when `company` is absent. If `roles` produces usable filters, it takes precedence over `role_family`. Surviving role filters are combined with AND; multiple titles within one title token are alternatives (OR). Invalid or duplicate tokens can be skipped rather than rejected, so inspect the echoed query and returned matches.
</ParamField>

Example role values:

* `roles=forward-deployed-engineer:18@company`
* `roles=software-engineer:24,founding-engineer:12`
* `roles=t.contains.Staff%20ML%20Engineer.12`
* `roles=t.exact.Staff%20ML%20Engineer;Principal%20Engineer.12@company`

## Text and education

<ParamField query="keywords" type="string">
  Free-text keyword match. The Gateway requires at least one character. Use `q_in` to select the field.
</ParamField>

<ParamField query="q_in" type="string" placeholder="people">
  Where `keywords` applies: `people` (default, full-text over name and headline), `name`, or `headline`.
</ParamField>

<ParamField query="school" type="string">
  Case-insensitive school-name fragment. The Gateway requires at least one character.
</ParamField>

## Skills and languages

<ParamField query="skill" type="string">
  Single skill term, kept as a compatibility alias. Prefer `skills` for multiple terms. The Gateway requires at least one character.
</ParamField>

<ParamField query="skills" type="string" placeholder="Foundry;Palantir">
  Semicolon-, comma-, or `or`-separated terms. The request value is at most 800 characters; up to 8 distinct terms are parsed and each term is limited to 64 characters. Each term matches a LinkedIn skill, current or past employer, or headline. Terms use OR matching by default, or AND matching with `skill_match=all`.
</ParamField>

<ParamField query="skill_match" type="string" default="any">
  `any` (OR) or `all` (AND) for `skills`. The default is `any`.
</ParamField>

<ParamField query="language" type="string">
  Single spoken-language term, kept as a compatibility alias. Prefer `languages` for multiple terms. The Gateway requires at least one character.
</ParamField>

<ParamField query="languages" type="string" placeholder="German;French">
  Semicolon-, comma-, or `or`-separated spoken languages. The request value is at most 800 characters; up to 8 distinct terms are parsed and each term is limited to 64 characters. Terms match Language graph nodes only, not employers or headlines. Terms use OR matching by default, or AND matching with `language_match=all`.
</ParamField>

<ParamField query="language_match" type="string" default="any">
  `any` (OR) or `all` (AND) for `languages`. The default is `any`.
</ParamField>

<ParamField query="exclude_languages" type="string" placeholder="English;French">
  Semicolon-, comma-, or `or`-separated spoken languages to exclude. The request value is at most 800 characters; up to 8 distinct terms are parsed. This exclusion is independent of `languages`; a person matching any excluded Language node is dropped.
</ParamField>

<ParamField query="certification" type="string" placeholder="AWS">
  Single-term compatibility alias for `certifications`. The value is a 1–200 character, case-insensitive literal substring matched against the name or issuing authority of an actual recorded certification edge. This indicates a recorded credential; it does not verify that the certification is current, valid, or unexpired. This filter combines with every other people filter using AND.
</ParamField>

<ParamField query="certifications" type="string" placeholder="[&#x22;AWS&#x22;,&#x22;Azure&#x22;]">
  String containing a JSON list of up to 10 distinct terms after trimming and case-insensitive deduplication. Each term is 1–200 characters and is matched as a case-insensitive literal substring of a recorded certification name or issuing authority. The terms use OR matching by default, or AND matching with `certification_match=all`. This indicates recorded credentials; it does not verify that a certification is current, valid, or unexpired. The legacy singular `certification` parameter remains supported.
</ParamField>

<ParamField query="certification_match" type="string" default="any">
  `any` (OR) or `all` (AND) for `certifications`. The default is `any`. This has no effect when no certification terms are supplied.
</ParamField>

The people search typeahead endpoint, `GET /suggest?q=...`, also returns `certification` suggestions. Each suggestion uses the existing certification filter: `title` is the credential name, `key` is its normalized name, and `count` is the number of distinct audience-visible, unflagged people holding that named credential. Renewals and duplicate certification edges count once per person. Suggestions may include `authority` when the name has one unambiguous issuer.

## Location

<ParamField query="location" type="string" placeholder="Europe;Canada">
  Up to 20 semicolon-separated cities, regions, countries, or continents. Each selection is trimmed to 120 characters. A comma stays inside a place, such as `Paris, France`. Included locations are ORed. Continents expand to their configured country groups.
</ParamField>

<ParamField query="exclude_locations" type="string" placeholder="France;London">
  Up to 20 semicolon-separated locations, with a maximum request length of 2420 characters and 120 characters per selection. Exclusions take priority over included locations. Without `location`, the search runs everywhere except these places.
</ParamField>

## Availability and data quality

<ParamField query="open_to_work" type="boolean">
  Match the profile's LinkedIn-reported open-to-work flag. Both `true` and `false` are accepted.
</ParamField>

<ParamField query="between_roles" type="boolean">
  Match whether the person has recorded work history and no position marked current. Both `true` and `false` are accepted.
</ParamField>

<ParamField query="exploring" type="boolean">
  Match FreshTalent's exploring assessment. It is distinct from open-to-work and between-roles. Both `true` and `false` are accepted.
</ParamField>

<ParamField query="github_hireable" type="boolean">
  Match GitHub's reported `hireable` field. Both `true` and `false` are accepted.
</ParamField>

<ParamField query="include_flagged" type="boolean">
  Include profiles with data-quality flags when `true`. The default is `false`.
</ParamField>

## Pagination

<ParamField query="limit" type="integer" default="25">
  Page size from `1` to `100`.
</ParamField>

<ParamField query="offset" type="integer" default="0">
  Zero-based number of matching rows to skip. Must be `0` or greater.
</ParamField>

Advance `offset` by the returned `meta.limit` while `meta.has_more` is `true`. There is no cursor or `next` URL. `meta.total` is `null` for an unfiltered browse and otherwise contains the count calculated for the filter; `meta.count` is the number returned on this page. `has_more` currently means the returned page is full, not that another row has been proven to exist. A final extra request may return an empty page. Offset paging is not snapshot-isolated; concurrent graph updates can shift rows.

<RequestExample>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --max-time 30 --get "https://api.freshtalent.ai/v1/people" \
    --data-urlencode "company=Palantir" \
    --data-urlencode "roles=forward-deployed-engineer:18@company" \
    --data-urlencode "view=compact" \
    --data-urlencode "limit=25" \
    -H "Authorization: Bearer $FRESHTALENT_API_KEY"
  ```

  ```javascript JavaScript (fetch) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const params = new URLSearchParams({
    company: "Palantir",
    roles: "forward-deployed-engineer:18@company",
    view: "compact",
    limit: "25",
  });
  const response = await fetch(`https://api.freshtalent.ai/v1/people?${params}`, {
    signal: AbortSignal.timeout(30_000), headers: { Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}` },
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
  const body = await response.json();
  console.log(body.data, body.meta.has_more);
  ```

  ```python Python (stdlib) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import json
  import os
  from urllib.parse import urlencode
  from urllib.request import Request, urlopen

  params = urlencode({
      "company": "Palantir",
      "roles": "forward-deployed-engineer:18@company",
      "view": "compact",
      "limit": "25",
  })
  request = Request(
      f"https://api.freshtalent.ai/v1/people?{params}",
      headers={"Authorization": f"Bearer {os.environ['FRESHTALENT_API_KEY']}"},
  )
  with urlopen(request, timeout=30) as response:
      body = json.load(response)
  print(body["data"], body["meta"]["has_more"])
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={"theme":{"light":"github-light","dark":"github-dark"}}
  {
    "data": [
      {
        "slug": "derek-morrow",
        "name": "Derek Morrow",
        "headline": "FDE, Anduril",
        "linkedin_url": "https://www.linkedin.com/in/derek-morrow",
        "current": { "company": "Anduril", "title": "FDE", "months": 14 },
        "match": { "company": "Palantir", "months": 50 },
        "open_to_work": false,
        "between_roles": false,
        "exploring": false,
        "github_hireable": true
      }
    ],
    "query": {
      "company": "Palantir",
      "roles": "forward-deployed-engineer:18@company"
    },
    "meta": {
      "count": 1,
      "limit": 25,
      "offset": 0,
      "has_more": false,
      "total": 1,
      "total_capped": false
    }
  }
  ```
</ResponseExample>

## Errors and recovery

* `400 invalid_request`: fix query validation, a value outside its limit, or the dependency error `min_role_months requires role_family` when neither `role_family` nor `roles` is present.
* `401 unauthorized`: provide a valid API key in `Authorization: Bearer ...`.
* `402 payment_required`: restore active organization access.
* `5xx` or a transient network failure: retry this read with bounded backoff, jitter, and a timeout.
* `429`: slow down and retry with exponential backoff and jitter. The shared Gateway limit is 300 requests per minute per client IP as the Gateway sees it. Honor `Retry-After`; see [Rate limits](/rate-limits).

A `GET` can be retried after a transient `429`; do not retry a `400` until the request is corrected. See [Errors](/errors) for the common envelope and retry rules.

<Tip>
  Persist the echoed `query` object with [Save query](/api-reference/queries/save), or reuse its fields on a later people search. Target creation uses a different schema and discovery semantics; see [Create target](/api-reference/targets/create#search-query-compatibility) before translating filters. Open a row with [Get person](/api-reference/people/get) when you need the full dossier.
</Tip>
