Skip to main content
POST
/
v2
/
developer
/
deep-search
curl -X POST "https://api.orbitsearch.com/v2/developer/deep-search" \
  -H "Authorization: Bearer sk_orb_REDACTED" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10",
    "runShape": "partial_plus_full",
    "linkedinUrl": "https://www.linkedin.com/in/janedoe",
    "email": "jane.doe@example.com"
  }'
{
  "request_id": "8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10",
  "run_id": "RUN_ID",
  "status": "started",
  "links": {
    "status": "/v2/developer/deep-search/8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10"
  }
}
Developer Deep Search starts an asynchronous profile generation run from one or more identity signals — an existing 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.
This endpoint replaced its previous contract in place. The legacy mode, partialProfile, dry_run, and options fields are rejected with a 400 developer_deep_search_field_unsupported error that names the legacy field and its replacement. Runs are always real — there is no dry run. See Migrating from the previous contract.
curl -X POST "https://api.orbitsearch.com/v2/developer/deep-search" \
  -H "Authorization: Bearer sk_orb_REDACTED" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10",
    "runShape": "partial_plus_full",
    "linkedinUrl": "https://www.linkedin.com/in/janedoe",
    "email": "jane.doe@example.com"
  }'
{
  "request_id": "8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10",
  "run_id": "RUN_ID",
  "status": "started",
  "links": {
    "status": "/v2/developer/deep-search/8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10"
  }
}

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.
requestId
string
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.
profileId
string
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.
runShape
string
default:"partial_only"
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.
structuredIntent
object
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.
email
string
Optional email address to use as an identity signal.
phoneNumber
string
Optional phone number to use as an identity signal. Send the field as phoneNumber, not phone.
linkedinUrl
string
Optional LinkedIn profile URL to use as an identity signal.
urls
string[]
Optional public http(s) URLs to use as identity signals.
usernames
string[]
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 shapeWhat gets generatedWhen 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_fullThe 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_onlyOnly the final, fully enriched profile (no fast partial).You only care about the final fully enriched profile.
candidate_discoveryMultiple 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.
If the provided identity signals cannot start a Deep Search run, the request is rejected with 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

Send profileId 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
{
  "request_id": "regen-1",
  "run_id": null,
  "status": "completed",
  "result": {
    "id": "ORBIT_PROFILE_ID"
  },
  "links": {
    "profile": "/v2/developer/profiles/ORBIT_PROFILE_ID"
  }
}

Idempotency with requestId

Idempotency is handled by the requestId 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 requestId202 with status: "started" and a new run starts.
  • Repeat request with the same requestId200 with status: "already_running" and the same identifiers; no duplicate run starts.
  • Omitted requestId → the server generates one and returns it as request_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.
request_id
string
required
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.
run_id
string | null
required
An opaque tracking id for this run, or null when no workflow was started.
status
string
required
"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.
result
object | null
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 with 400 developer_deep_search_field_unsupported, and the error message names the replacement:
Legacy fieldReplacement
mode: "generation"Omit; profile generation is the default behavior
mode: "candidate_discovery"runShape: "candidate_discovery"
partialProfile: truerunShape: "partial_only" (default), or "partial_plus_full"
dry_runRemoved; runs are always real
optionsRemoved
profile_id or orbit_idprofileId
Idempotency-Key headerrequestId body field
GET .../{id}/events (SSE)Poll GET /v2/developer/deep-search/{requestId}
Identity signals are also more permissive than before: any one of 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. A 429 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 the X-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

CodeDescription
developer_deep_search_field_unsupportedThe 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_invalidurls or usernames is not an array of strings
developer_deep_search_url_invalidurls contains a value that is not a valid http(s) URL
developer_deep_search_email_invalidemail is malformed
developer_deep_search_phone_invalidphoneNumber is malformed
developer_deep_search_linkedin_invalidlinkedinUrl is malformed or is not a LinkedIn profile URL
developer_deep_search_run_shape_invalidrunShape is not one of partial_only, partial_plus_full, full_generation_only, candidate_discovery
developer_deep_search_request_id_invalidrequestId is not a non-empty string
developer_deep_search_profile_id_invalidprofileId is not a non-empty string
developer_deep_search_input_requiredNo identity signal was provided
developer_deep_search_personalization_unsupportedstructuredIntent.personalization was provided; Deep Search cannot honor user-context filters
developer_deep_search_unprocessable_seedThe provided identity signals could not start a Deep Search run.
developer_deep_search_invalid_requestThe request body was rejected; the message describes what to fix.
structured_intent_requiredstructuredIntent was provided but its value is not a valid object
structured_intent_invalidstructuredIntent is malformed or contains unsupported values
structured_intent_version_unsupportedstructuredIntent.version must be "v1"
structured_intent_entity_clauses_unsupportedentityClauses are not part of the public structured DSL
structured_intent_server_owned_fieldThe request includes a server-owned structured intent field
CodeDescription
developer_api_credits_insufficientYour remaining credits are lower than the run cost. The error includes requiredCredits and remainingCredits.
CodeDescription
developer_api_idempotency_key_conflictThe requestId was already used with different request parameters
developer_api_idempotency_key_refundedThe requestId belongs to a run whose charge was refunded; use a new requestId
CodeDescription
developer_api_key_rate_limitedThe API key exceeded the search rate limit
CodeDescription
developer_deep_search_request_failedDeep Search did not accept the request or could not be reached. Retry later.
CodeDescription
developer_deep_search_unavailable_for_requestDeep Search is not available for this request.
developer_deep_search_unavailableDeep Search is currently unavailable. Retry later.
developer_deep_search_not_configuredDeep Search is not configured in this environment

Start a run from a structured intent seed

curl -X POST "https://api.orbitsearch.com/v2/developer/deep-search" \
  -H "Authorization: Bearer sk_orb_REDACTED" \
  -H "Content-Type: application/json" \
  -d '{
    "structuredIntent": {
      "version": "v1",
      "names": ["Jane Doe"],
      "experiences": [{ "organization": "Example Labs" }],
      "semanticClauses": [{ "text": "writes about developer tools" }]
    }
  }'

Discover candidate profiles from a structured intent

curl -X POST "https://api.orbitsearch.com/v2/developer/deep-search" \
  -H "Authorization: Bearer sk_orb_REDACTED" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10",
    "runShape": "candidate_discovery",
    "structuredIntent": {
      "version": "v1",
      "names": ["Sam Altman"],
      "experiences": [
        { "organization": "OpenAI", "title": "CEO", "temporalScope": "current" }
      ]
    }
  }'