Start a Deep Search run
Start an async Deep Search profile generation run from one or more identity signals using a developer API key.
POST
Developer Deep Search starts an asynchronous profile generation run from one or more identity signals — an existing
If the provided identity signals cannot start a Deep Search run, the request is rejected with
Identity signals are also more permissive than before: any one of
profileId, LinkedIn URL, email, phone number, public URLs, usernames, or a structured search intent. Depending on the run shape you choose, the generated profile can be produced partial-first: a fast, source-backed partial profile can become available before full generation finishes. Slim partial run shapes return a lighter-weight partial profile first for lower-latency preview use cases. You can also use candidate_discovery to discover multiple possible people from a structuredIntent.
After starting a run, poll GET /v2/developer/deep-search/{requestId} until the run reaches completed or failed. For single-profile run shapes, generating_sections means Deep Search has a profile id, but generated profile sections are still being written. completed means profile section generation has finished; individual sections may be skipped or fail. Check result.section_generation.sections_ready and result.section_generation.materialized_section_count to see whether generated public sections were actually produced. If no sections were materialized, the profile-read response may contain only basic profile data. Failed status responses may include a sanitized failure.code, message, and suggested retry inputs; some failed responses can still include result.id when the profile was saved but generated profile sections were not ready before the readiness timeout. Single-profile status responses return the durable profile resource id and section-generation summary. candidate_discovery returns candidate profile IDs as they are created. Fetch generated profile bodies separately with GET /v2/developer/profiles/{id}. There is no streaming endpoint; the previous SSE events route has been removed.
Requests authenticate with a developer API key that has the search:read scope. See Authentication.
Body parameters
The request body is a strict allowlist:requestId, runShape, profileId, structuredIntent, email, phoneNumber, linkedinUrl, urls, and usernames. Any other top-level field is rejected. At least one identity signal — profileId, structuredIntent, email, phoneNumber, linkedinUrl, urls, or usernames — is required.
Optional idempotency key for the run. Re-sending the same
requestId returns 200 with status: "already_running" instead of starting a duplicate run. When omitted, the server generates one and returns it as request_id.Optional existing Orbit profile ID to regenerate from. Deep Search uses stored profile context, such as prior profile data, phone, LinkedIn URL, age, location, and known addresses, plus any additional identity signals you send in the same request. If the profile is already fully generated, the request returns
200 with status: "completed" and result.id; no new workflow starts.Optional run shape:
"partial_only" (default), "partial_plus_full", "full_generation_only", or "candidate_discovery". Controls what the run produces and when. See Run shapes. Replaces the legacy mode and partialProfile fields. When profileId is provided, candidate_discovery is not valid.Optional structured search DSL object (
{ "version": "v1", "names": [...], "semanticClauses": [...], "experiences": [...], "schools": [...], "geo": {...}, "demographics": {...} }) — the same format as the structured search endpoints. personalization is not supported here. See Structured DSL.Optional email address to use as an identity signal.
Optional phone number to use as an identity signal. Send the field as
phoneNumber, not phone.Optional LinkedIn profile URL to use as an identity signal.
Optional public
http(s) URLs to use as identity signals.Optional usernames to use as identity signals.
Deep Search does not have the synchronous search endpoint’s API-key user network or location context, so
structuredIntent.personalization is rejected here. Use POST /v2/developer/search when you need personalized network or near-me search behavior.Run shapes
| Run shape | What gets generated | When to use it |
|---|---|---|
partial_only (default) | A fast, source-backed partial profile, returned quickly and then enriched in place by a retroactive background backfill (no further full generation). | You want the quickest usable profile and do not need full generation. |
partial_plus_full | The fast partial profile first, then a fully enriched profile afterwards. | You want a profile available quickly and a fully enriched profile to land later. |
full_generation_only | Only the final, fully enriched profile (no fast partial). | You only care about the final fully enriched profile. |
candidate_discovery | Multiple partial candidate profiles discovered from structuredIntent. The status result includes candidate_profile_ids, candidate_count, dispatched_candidate_count, persisted_candidate_count, current_phase, and timing/search-count metrics. | Your initial POST /v2/developer/search result did not include the profile you wanted, and you need Orbit to search for possible matching people. |
400 developer_deep_search_unprocessable_seed.
candidate_discovery requires structuredIntent and does not accept profileId. It runs source discovery from the structured intent, clusters the sources into possible people, and saves source-backed partial Orbit profiles for the candidates. Candidate profiles include inherited cluster sources, images when available, bio sections, and available fun facts. They do not continue into full generation and are not marked as full generation complete. Deep Search may discover more candidate clusters than it generates immediately; polling reports the discovered candidate count, the dispatched partial-profile count, and any skipped candidates separately.
Use candidate_discovery after a normal POST /v2/developer/search response does not contain the person you are trying to find. It is for discovering possible candidate people from public sources, not for fully enriching one known profile.
Regenerate from an existing profile
SendprofileId when you already have an Orbit profile ID from Search, Profile Read, or a previous Deep Search result. The developer API starts a Deep Search run from that profile’s stored context and any extra identity signals you include in the request body.
Use the same generation shapes as new runs: partial_only, partial_plus_full, or full_generation_only. candidate_discovery is rejected with 400 developer_deep_search_run_shape_invalid when profileId is present. When profileId is the only identity signal and runShape is omitted, the regeneration path defaults to full generation.
If the existing profile is already fully generated, Deep Search does not start a workflow. The response is immediate:
200
Idempotency with requestId
Idempotency is handled by therequestId body field rather than a header. Generate a UUID per logical run and re-send the same requestId on retries:
- First request with a new
requestId→202withstatus: "started"and a new run starts. - Repeat request with the same
requestId→200withstatus: "already_running"and the same identifiers; no duplicate run starts. - Omitted
requestId→ the server generates one and returns it asrequest_id. Store it; it is also the path parameter for status polling.
Response
202 when a new run was started, 200 when an existing run was reused or when a profileId request completed immediately because the profile is already fully generated.
The run’s durable id — your
requestId if you sent one, otherwise server-generated. Use it as the path parameter when polling status and as the idempotency key on retries.An opaque tracking id for this run, or
null when no workflow was started."started" when a new run was started, "already_running" when the same requestId was reused and no new run was started, or "completed" when a profileId request completed immediately.links.status is the relative URL of the status endpoint for a started or reused run. links.profile is returned for an immediately completed profileId request.Present only for an immediately completed
profileId request. The profile resource ID is returned as result.id, matching Search results and Profile Read payloads.Migrating from the previous contract
The endpoint paths are unchanged, but the request and response contracts are new. Legacy fields are rejected with400 developer_deep_search_field_unsupported, and the error message names the replacement:
| Legacy field | Replacement |
|---|---|
mode: "generation" | Omit; profile generation is the default behavior |
mode: "candidate_discovery" | runShape: "candidate_discovery" |
partialProfile: true | runShape: "partial_only" (default), or "partial_plus_full" |
dry_run | Removed; runs are always real |
options | Removed |
profile_id or orbit_id | profileId |
Idempotency-Key header | requestId body field |
GET .../{id}/events (SSE) | Poll GET /v2/developer/deep-search/{requestId} |
profileId, structuredIntent, email, phoneNumber, linkedinUrl, urls, or usernames is enough to start a run. If the provided signals cannot start a run, the request fails with developer_deep_search_unprocessable_seed instead of a field-combination validation error.
Rate limits
Deep Search start requests have their own rate-limit bucket: 5 requests per second per API key, with bursts up to 25 requests. Status polling uses a separate bucket documented on Deep Search status. A429 response does not consume credits.
Credits
Starting a run costs 10 credits by default (your operator may configure a different value). Credits are reserved before the run starts and the response includes your post-charge balance in theX-Developer-API-Credits-Remaining header. Status polling (GET) is free.
The charge is idempotent on requestId: retrying the same requestId does not charge again, and if your request reuses an already-running run (status: "already_running") a fresh charge is refunded. If a profileId request completes immediately because the profile is already fully generated, no new run starts and the reserved Deep Search charge is refunded. Charges are also refunded automatically when the run is rejected or cannot be accepted. Reusing a requestId with different request parameters fails with 409 developer_api_idempotency_key_conflict, and reusing one whose charge was refunded fails with 409 developer_api_idempotency_key_refunded. If your balance cannot cover the cost, the request fails with 402 developer_api_credits_insufficient (including requiredCredits and remainingCredits) before any work is done.
Error responses
400 - Bad request
400 - Bad request
| Code | Description |
|---|---|
developer_deep_search_field_unsupported | The body includes a top-level field other than requestId, runShape, profileId, structuredIntent, email, phoneNumber, linkedinUrl, urls, or usernames. Legacy fields (mode, partialProfile, dry_run, options, profile_id, orbit_id) are flagged with a replacement hint. |
developer_deep_search_field_invalid | urls or usernames is not an array of strings |
developer_deep_search_url_invalid | urls contains a value that is not a valid http(s) URL |
developer_deep_search_email_invalid | email is malformed |
developer_deep_search_phone_invalid | phoneNumber is malformed |
developer_deep_search_linkedin_invalid | linkedinUrl is malformed or is not a LinkedIn profile URL |
developer_deep_search_run_shape_invalid | runShape is not one of partial_only, partial_plus_full, full_generation_only, candidate_discovery |
developer_deep_search_request_id_invalid | requestId is not a non-empty string |
developer_deep_search_profile_id_invalid | profileId is not a non-empty string |
developer_deep_search_input_required | No identity signal was provided |
developer_deep_search_personalization_unsupported | structuredIntent.personalization was provided; Deep Search cannot honor user-context filters |
developer_deep_search_unprocessable_seed | The provided identity signals could not start a Deep Search run. |
developer_deep_search_invalid_request | The request body was rejected; the message describes what to fix. |
structured_intent_required | structuredIntent was provided but its value is not a valid object |
structured_intent_invalid | structuredIntent is malformed or contains unsupported values |
structured_intent_version_unsupported | structuredIntent.version must be "v1" |
structured_intent_entity_clauses_unsupported | entityClauses are not part of the public structured DSL |
structured_intent_server_owned_field | The request includes a server-owned structured intent field |
402 - Insufficient credits
402 - Insufficient credits
| Code | Description |
|---|---|
developer_api_credits_insufficient | Your remaining credits are lower than the run cost. The error includes requiredCredits and remainingCredits. |
409 - Idempotency conflict
409 - Idempotency conflict
| Code | Description |
|---|---|
developer_api_idempotency_key_conflict | The requestId was already used with different request parameters |
developer_api_idempotency_key_refunded | The requestId belongs to a run whose charge was refunded; use a new requestId |
429 - Rate limited
429 - Rate limited
| Code | Description |
|---|---|
developer_api_key_rate_limited | The API key exceeded the search rate limit |
502 - Upstream error
502 - Upstream error
| Code | Description |
|---|---|
developer_deep_search_request_failed | Deep Search did not accept the request or could not be reached. Retry later. |
503 - Service unavailable
503 - Service unavailable
