Search people profiles

Run a search query. This accepts a natural language query as query parameter along with additional settings. It returns a list of profiles that match the query and orders them by relevance.
The query can be:

  • a simple search query like "software engineer from Ohio"
  • a more complex query like "Find me engineers in California speaking at least basic english working in software industry with experience at FAANG with 2+ years of experience and at least 500 followers and at least BS degree".
  • a full JD-style query that describes the ideal candidate in detail along with job description, responsibilities, requirements, etc.

If the query is already structured (for example JSON, structured text, or a boolean expression), you have two main options:

  • Use the query parameter and pass the structured query as is. This is the simplest approach. Pearch.ai will interpret structured parts and priorities written in free-form text (such as “must have” or “very important”).
  • Use the custom_filters parameter and map some or all structured parts to it. This gives you more control over filtering.

Use parameters to control tradeoffs between speed and quality. Here are some recipes:

  • For highest quality results (30-180 seconds) use pro search with insights and profile scoring enabled: {"query": "<YOUR QUERY HERE>", "type": "pro", "insights": true, "profile_scoring": true}
  • For medium to high quality results use "fast" search (<50s). {"query": "<YOUR QUERY HERE>", "type": "fast", "insights": true, "profile_scoring": true}
  • For pagination, pass the previous response's thread_id with limit and optionally offset. Omit the original query and search/filter parameters. To wait for an async search, send only thread_id instead; see Async search and polling below.

Results are generally returned in the order of decreasing relevancy. However, you may notice results with out of order score values in some cases especially when requesting large number of results. It should be safe to use stable sorting by score field on the client side in this cases. Note that score field is only calculated and returned when profile_scoring=true.

Check out other parameters below to adjust speed vs quality and additional data that can be returned by the endpoint.

Async search and polling

Start a search with POST /v2/search and async=true. Include your API key in the Authorization: Bearer <API_KEY> header and send a JSON body:

{"query":"Software engineer with Python experience in London","type":"fast","limit":10,"async":true}

A newly started background search returns HTTP 202 with thread_id, task_id, and uuid (equal to task_id). Save thread_id to retrieve results. Keep task_id to correlate a completion webhook; separate searches or pagination tasks can share the same thread.

To wait for completion, repeat POST /v2/search with only thread_id:

{"thread_id":"<thread-uuid>"}

Do not resend the query, filters, async, webhook_url, limit, or offset in this polling request.

  • HTTP 202 with status equal to pending, running, or streaming: the thread has an active search. Wait before polling again; intermediate statuses may be skipped. A polling interval of a few seconds and an overall deadline are recommended client behavior.
  • HTTP 200 with status="Done": read search_results directly from the response. There is no nested result envelope. A bare thread read returns the currently stored results, which can include earlier pages; it is not restricted to the last requested page.
  • HTTP 4xx/5xx: handle the HTTP error rather than interpreting it as an in-progress status. An unknown thread returns 404. A thread with no reusable completed state can return 409, for example after its initial search fails. Do not retry every 409 indefinitely.

A cached request can return HTTP 200 immediately even when async=true; process that response without waiting for 202. Completed/cached reads have their own uuid, which need not equal the original task ID.

Thread polling retrieves the thread's current state, not a history of individual task outcomes. If a later task fails, previously completed results can remain available. Use the completion webhook's task_id and task_status when you need the outcome of a specific task.

Pagination after an async search

Wait for completion before requesting additional results. To request a specific page, send thread_id, limit, and offset to the same endpoint:

{"thread_id":"<thread-uuid>","offset":10,"limit":10,"async":true}

A page already stored in the thread returns HTTP 200. If additional search work is needed, async=true starts it in the background and returns HTTP 202; poll again with only thread_id. After completion, send the explicit page bounds again to retrieve just that page. Without async=true, additional search work runs synchronously.

A request with limit or offset is pagination, not a status poll. While a search is active, requesting more profiles than are currently stored returns HTTP 409. For example, repeating the initial limit before any results have been stored can return 409. The existing search continues; wait using the bare thread polling request. A page already available in the completed state may still return HTTP 200 while another task runs, so that page's 200 response does not prove the active task finished.

Re-reading already billed results does not charge for those results again. Requesting additional results can consume credits. Omit the original query and search/filter parameters from pagination requests; changing the query or supplying filters with thread_id is rejected with HTTP 400 for standard clients.

Async search callbacks

As an alternative to polling, include webhook_url with async=true in the request that starts a search or an additional page.

Supply webhook_url, for example https://client.example.com/callback?token=<your-webhook-secret>. Use HTTPS and a dedicated secret generated and verified by your application. The supplied URL, including query parameters, is used for delivery. Callbacks have no HMAC signature or timestamp header; the URL secret does not provide cryptographic payload integrity or timestamp-based replay protection.

Each callback includes uuid, task_id (equal to uuid), thread_id, and task_status. Deduplicate by task_id, since separate tasks can share a thread. Every retry sends identical JSON bytes.

A successful callback preserves the synchronous result fields, including status="Done" and search_results, and adds task_id and task_status="completed". Polling through /v2/search instead returns HTTP 200 with status="Done" and top-level search_results.

Example successful callback (other search-result fields omitted):

{"uuid":"<task-uuid>","task_id":"<task-uuid>","thread_id":"<thread-uuid>","task_status":"completed","status":"Done","search_results":[]}

A failed callback includes status="failed", task_status="failed", http, and a public error message. Optional code and action fields are preserved. For example:

{"uuid":"<task-uuid>","task_id":"<task-uuid>","thread_id":"<thread-uuid>","task_status":"failed","status":"failed","http":500,"error":"Internal server error"}

Compatibility: older failed callbacks used status for the public error message. New failed callbacks use status="failed" and put that message in error. When accepting both formats, detect errors using http >= 400, prefer a nonempty string error for the message and fall back to status. Allow additional fields and treat newly added identifiers and task_status as optional when parsing legacy callbacks. Use a separate error model rather than the successful search-response model.

Handled cancellations use http=499 and error="Search task was cancelled before completion.". Requests rejected before a task is accepted return an HTTP error directly and do not produce a completion callback.

Delivery makes at most four attempts: one immediately, then retries after waits of 1, 2, and 4 seconds following the previous failure. Every non-2xx response or transport error triggers a retry. Each attempt uses a 10-second connection/read timeout; redirects are not followed. Return any 2xx promptly after safely accepting the event, including duplicates. Failed delivery does not rerun the search or change its completion status.

Retries run in the API worker, without a durable delivery queue. A process crash or restart can interrupt delivery, and delivery stops after the fourth failure. If a callback is missing, retrieve the current thread state with POST /v2/search and only thread_id, as described above. This recovers available thread results, not a guaranteed per-task failure history. For cached requests (HTTP 200), process the result in that HTTP response. Keep the requested page bounds if you need to reload a specific page after completion.

Cost: (number of profiles returned) × (credits for type + insights + high_freshness + reveal_emails + reveal_phones).

Rate limit: 60 actual search requests per user per 60 seconds. /v2/search and /v2/search/count share this limit with legacy search submissions. Fully cached thread reloads that do not run a new search use a separate higher limit of 120 cached requests per user per 60 seconds. When the rate limit is exceeded, the API returns HTTP 429 with details about the triggered window, current value, and limit.

Body Params
string
Defaults to Software engineer with Python experience from Florida who is interested in science

The human readable search query string. The query can be a simple search query like "software engineer from Ohio" or a more complex query like "Find me engineers in California speaking at least basic english working in software industry with experience at FAANG with 2+ years of experience and at least 500 followers and at least BS degree". Query parameter can handle full job descriptions, but passing specific subset of important requirements normally works better. Provide query, search_requirements, or non-empty custom_filters when thread_id is omitted. A request with only custom_filters uses exact filter retrieval without semantic query ranking. If both query and search_requirements are provided, search_requirements takes precedence for search execution.

search_requirements
array of objects

Structured search requirements. Use this when you already have a list of requirement texts and whether each one is mandatory. Each item supports only requirement text and must_have. If this parameter is provided, it overrides query for search execution.

search_requirements
string

Search-thread identifier used both for async polling and pagination. Poll with only thread_id: HTTP 202 while a search is active; HTTP 200 with top-level search_results when completed state is available. Add limit and offset only to request a page; requesting additional results during an active search returns HTTP 409. Omit query and search/filter parameters. Changing the query or supplying filters with thread_id returns HTTP 400 for standard clients. Re-reading already billed results does not charge for those results again. See Async search and polling above.

string
enum
Defaults to pro

The type of search to perform.

  • "pro" is slower and more costly, but has higher quality and recall. Cost: 5 credits per candidate
  • "fast" is faster but may have less recall. Cost: 1 credit per candidate
Allowed:
integer
≥ 1

Target search time budget in seconds. Defaults to 30 for fast search and 300 for pro search. Pro search requires at least 60 seconds.

boolean
Defaults to true

Enable detailed insights and analysis for search results. This includes scores for each of query requirements along with quotes from profiles that match subqueries. Check out insights output fields for details. This may be helpful if you share search results with users and want to provide deeper explanations of why profiles are relevant. If your product only needs highly relevant profiles without explanations, use {"insights": false, "profile_scoring": true} Cost: 1 credit per candidate

insights_items
array of strings

When insights is true, optionally restrict which insight fields to include. If omitted, all insight types are included.

insights_items
Allowed:
boolean
Defaults to false

Enable real-time profile updates for fresher data. This downloads the most recent copies of profiles directly from source websites in realtime (e.g. Linkedin). If you are ok with profiles that may be a few months outdated, and want to save ~30 seconds, turn off high_freshness. Refreshed roles include employment_type when the source provides it. Cost: 2 credits per candidate

boolean
Defaults to false

Fill below-limit responses with low confidence results when profile scoring exhausts candidates before reaching the requested limit. These fallback profiles are marked with their score and remain free for search and scoring. When contact reveal is requested, fallback profiles are enriched like regular results and credits_charged includes only contact data actually returned.

custom_filters
object

Custom filters in JSON format for advanced filtering. Omit the parameter to use default filtering logic.
Format: {"filter_name1": ["value1", "value2"], "filter_name2": ["value3", "value4"], ...}.

Filtering logic: Conditions between different keys are joined with "AND", so all conditions must pass. Within each condition, we always use "OR". For example, {"titles": ["swe", "mle"]} means the profile must have either "swe" OR "mle" as a title. If you specify multiple keys like {"titles": ["swe", "mle"], "locations": ["San Francisco"]}, the profile must match at least one title (swe OR mle) AND be in San Francisco.

industries and not_industries take canonical industry names listed under Industry; matching is case-insensitive. In fast and superfast search and in /v2/search/count, an unknown industry returns HTTP 400. In pro search, other strings are mapped semantically to canonical industries. An industry matches any employer in the person's career history, not only the current one.
If you want to force no filtering at all (custom_filters or default filters), pass empty object "custom_filters": {}. This will speed up search, increase recall at some relevancy cost.

string
enum

Mode for handling custom filters. 'exact' - only use the passed custom_filters, no LLM-generated filters. 'smart' - merge custom_filters with LLM-generated filters.

Allowed:
boolean
Defaults to false

Be stricter when filtering. If true, titles must match exactly after expanding abbreviations (e.g., "CEO" → "Chief Executive Officer") or translating (e.g., "Ingeniero de software" → "Software Engineer"). If false, fuzzy matching is also used. All other filters are always applied strictly. Enabling strict_filters improves quality but can greatly reduce the number of results. This setting only affects Pearch's implicit filtering. Custom filters are not affected and are always applied strictly.

boolean
Defaults to false

Only return profiles with at least one email address. Profiles will have additional has_email field set to true. Cost: 1 credit per candidate. Deprecated name: require_emails

boolean
Defaults to false

Include email addresses in search results if available. This returns all available emails including personal and work emails. Cost: 6 credits per candidate, only charged if the candidate has at least one email address. Deprecated name: show_emails

boolean
Defaults to false

Only return profiles with at least one phone number. Profiles will have additional has_phone_numbers field set to true. Cost: 1 credit per candidate. Deprecated name: require_phone_numbers

boolean
Defaults to false

Only return profiles with at least one phone number OR at least one email address. Useful when you need contact information but either email or phone is acceptable. Cost: 1 credit per candidate. Deprecated name: require_phones_or_emails

boolean
Defaults to false

Include phone numbers in search results if available. This returns all available phone numbers including personal and work numbers. Cost: 6 credits per candidate, only charged if the candidate has at least one phone number. Deprecated name: show_phone_numbers

integer
1 to 1000
Defaults to 10

The maximum number of search results to return.

integer
≥ 0
Defaults to 0

The number of search results to skip before returning results. Used for pagination in combination with limit.

docid_blacklist
array of strings

List of profile docids to exclude from search results. Use this to filter out profiles that have already been seen or contacted.

docid_blacklist
docid_whitelist
array of strings

List of profile docids to limit search results to. Only profiles with these docids will be returned.

docid_whitelist
boolean
Defaults to false

If true, new search work runs in the background and returns HTTP 202 with task_id, uuid (equal to task_id), and thread_id. Poll POST /v2/search with only thread_id, or supply webhook_url on the starting request. Cached results can return HTTP 200 immediately. Add limit/offset only for pagination, not polling.

uri

Callback URL for success, failure, or handled cancellation of an accepted task. Successful bodies preserve synchronous result fields and add task_id and task_status. See Async search callbacks above for authentication, retries, and error bodies. Requires async=true; HTTPS is recommended.

Responses

400

Invalid parameter values. Changing the query or supplying search/filter parameters with thread_id is rejected for standard clients. The public error body can use the generic status Bad Request.

401

Invalid API key

404

The requested thread was not found or is not accessible to this caller.

429

Rate limit exceeded. JSON body contains the triggered window, current value, and limit.

Language
Credentials
Bearer
JWT
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json