---
title: "Find people from public data"
method: POST
path: "/public/search"
tags: ["public"]
---

# Find people from public data

`POST /public/search`

Search for people through an anonymous public web search; no connected account is needed. Results are public data, never signed-in LinkedIn details (connection degree, who engaged with a post, whether someone is reachable by message). Found profiles are saved as contacts and render as a people list; narrate a one-line count. limit (1-300, default 15) is how many profiles ONE call targets: a specific number sets it, a volume word with no ceiling raises it toward 100-300. Pass every plausible title, seniority, or city as an array on the SAME filter field in ONE call. more:true continues the SAME filters past people already found. exhaustion:"axes-exhausted" means every angle was tried: suggest a real change, do not call more:true again. exhaustion:"batch-failed" is a temporary vendor hiccup. A signal that would show in a headline (years in role, a school, a tool, freelance) belongs in filters.keywords, not a visit-first pass.

## Request body

- object
  - `filters` object, required — Search facets. Provide at least one of title, location, company, industry, or keywords.
    - `title` union — Role or job title facet. One value or a list of alternatives.
      - string
      - string[]
    - `location` union — City, region, or country facet. One value or a list of alternatives.
      - string
      - string[]
    - `company` union — Company name facet. One value or a list of alternatives.
      - string
      - string[]
    - `industry` union — Industry facet. One value or a list of alternatives.
      - string
      - string[]
    - `seniority` union — Seniority facet. One value or a list of alternatives.
      - string
      - string[]
    - `keywords` string — Free-text keywords appended to the search query.
    - `countryCode` string — ISO alpha-2 country code used to scope results to one country.
  - `limit` integer — Target number of new profiles to return.
  - `save` boolean — When true, found profiles are saved to your contacts.
  - `chain` object — Optional two-step mode for DISCOVERING companies the caller does not already know by name (an industry, sector, or location ask): first find companies matching filters.industry/filters.location, then search this role inside each. Do NOT set this when specific companies are already named — pass them directly as filters.company instead (no chain needed): that searches those exact companies' roles directly, at lower cost and without a discovery step that can fail.
    - `role` string, required — Role searched inside each matched company.
    - `companiesLimit` integer — How many companies to search inside.
  - `more` boolean — Continue past the people an earlier search on these same filters already returned, instead of returning them again. What counts as already returned is who is saved, so this only advances while save stays on; with save off the same people come back and paging cannot move.

## Response `200`

Found and enriched people

- object
  - `source` 'public', required
  - `provider` string, required — The search provider that answered.
  - `query` string, required — The exact query sent, so a thin result set can be read rather than guessed at.
  - `existingCount` integer — People this search matched who were ALREADY in your contacts. They are linked into the target pool but not re-listed in `contacts`, because they were not re-fetched. Absent when there were none. Without it a caller reads a matched contact as a missed one.
  - `urlsFound` integer, required — Total profile URLs found.
  - `newUrls` integer, required — URLs not already in your contacts.
  - `hydrated` integer, required — Profiles fetched and enriched in this call.
  - `searches` integer — How many searches it took to find these people.
  - `searchesFailed` integer — Searches that did not reach the provider. When this is high the result says nothing about the market.
  - `moreAvailable` boolean — Whether asking again with more can still reach people you have not seen.
  - `exhaustion` 'more-available' | 'axes-exhausted' | 'batch-failed' — Finer than moreAvailable: 'axes-exhausted' means every search angle for this audience has genuinely been tried, calling again with more will not find anybody new. 'batch-failed' means this attempt could not reach the search provider, worth retrying. 'more-available' is the ordinary case.
  - `axesTried` object[] — What was actually tried, broken down by search angle, against what each angle could still do.
    - `axis` string, required — Which search angle this row covers, e.g. title, city, school, tool.
    - `queriesPlanned` integer, required — How many searches this angle could run in total.
    - `attempted` integer, required — How many of those have actually run so far.
    - `yielded` integer, required — New people this angle has contributed.
    - `exhausted` boolean, required — Whether this angle has nothing left to try.
  - `axesAvailable` object[] — Every search angle this audience supports and how many queries each could run — the full potential, whether or not it has been searched yet. Free to compute, no extra searches run to produce it.
    - `axis` string, required — A search angle this audience supports, e.g. title, city, school, tool.
    - `queries` integer, required — How many searches that angle could run in total.
  - `countryRiskNote` object — A non-blocking caveat about this market, e.g. a country whose profiles are hard to tell apart from a bigger anglophone one. Never a reason to withhold or shrink the result — surface it as a caution alongside the real count, never in place of one.
    - `risk` 'low' | 'medium' | 'high', required — How likely this country/title combination is to return false-positive matches from elsewhere.
    - `reason` string, required — Why, in plain language.
  - `rateLimited` integer — Profiles found but deferred because the scraping pool is busy.
  - `note` string — Explanation when part of the result set was deferred.
  - `contacts` object[], required
    - `name` string, nullable, required
    - `headline` string, nullable, required
    - `company` string, nullable, required
    - `location` string, nullable, required
    - `profileUrl` string, nullable, required
    - `completeness` string, required
    - `summary` string — The profile's About text, when the public page carried one.
    - `connections` integer — Connection count, when the public page carried one.
    - `imageUrl` string — The profile's display photo (from the public page's og:image), when the public page carried one. A raw LinkedIn CDN URL — may expire over time, unlike a re-hosted avatar.
    - `education` string — School name from the public page, when present.
    - `followers` integer — Public follower count from the preview page, when present. Not a login-only exact count.
    - `badge` string — Public profile badge from the preview page, when present.
    - `languages` string[] — Languages listed on the public preview page, when present.
    - `positions` object[] — Visible employers from the public preview page, when present.
      - `companyName` string, required
      - `title` string, nullable, required
      - `companyUrl` string, nullable, required
      - `startDate` object, nullable, required
        - `year` number, required
      - `endDate` object, nullable, required
        - `year` number, required
      - `isCurrent` boolean, required
    - `educations` object[] — Visible schools from the public preview page, when present.
      - `schoolName` string, required
      - `schoolUrl` string, nullable, required
      - `startDate` object, nullable, required
        - `year` number, required
      - `endDate` object, nullable, required
        - `year` number, required
    - `recentPosts` object[] — A sample of the person's own public feed posts from the preview page, when present. Search returns at most three; the saved contact keeps the fuller public list.
      - `url` string, nullable, required
      - `text` string, nullable, required
      - `likes` number, nullable, required
      - `datePublished` string, nullable, required

## Other responses

- `400` — The server cannot or will not process the request due to something that is perceived to be a client error.
- `401` — Although HTTP specifies "unauthorized", this response means "unauthenticated". Authenticate to continue. NOTE: 401 is also returned with code "linkedin_not_connected" when the caller IS authenticated but has no connected LinkedIn account — connect LinkedIn (not re-authenticate) to continue.
- `403` — The client does not have access rights to the content.
- `404` — The server cannot find the requested resource.
- `409` — The request conflicts with the current state of the server.
- `410` — The requested content has been permanently deleted from the server.
- `422` — The request was well-formed but was unable to be followed due to semantic errors.
- `429` — Rate limit exceeded. Read error.retryAfter for the wait time in seconds.
- `500` — The server encountered a situation it does not know how to handle.
- `502` — LinkedIn returned a server error or the proxy connection failed. Retry after a few seconds.
- `503` — Proxy capacity temporarily exceeded. Retry after a few seconds.

---

[API](https://skmtc.net/berea/apis/bereach-api.md) · [All operations](https://skmtc.net/berea/apis/bereach-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/berea/bereach-api/revisions/32391e7c6322/schema)
