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

# Start a Deep Search run

> Start an async Deep Search profile generation run from one or more identity signals using a developer API key.

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](/api/search/structured-dsl). 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}`](/api/search/deep-search-status) 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}`](/api/search/profile-read). 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](/authentication).

<Warning>
  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](#migrating-from-the-previous-contract).
</Warning>

<RequestExample>
  ```bash curl theme={"dark"}
  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"
    }'
  ```

  ```javascript JavaScript theme={"dark"}
  const requestId = crypto.randomUUID();

  const response = await fetch("https://api.orbitsearch.com/v2/developer/deep-search", {
    method: "POST",
    headers: {
      "Authorization": "Bearer sk_orb_REDACTED",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      requestId,
      runShape: "partial_plus_full",
      linkedinUrl: "https://www.linkedin.com/in/janedoe",
      email: "jane.doe@example.com",
    }),
  });

  const run = await response.json();
  console.log(run.request_id, run.status, run.links.status);
  ```

  ```python Python theme={"dark"}
  import uuid
  import requests

  request_id = str(uuid.uuid4())

  response = requests.post(
      "https://api.orbitsearch.com/v2/developer/deep-search",
      headers={
          "Authorization": "Bearer sk_orb_REDACTED",
          "Content-Type": "application/json",
      },
      json={
          "requestId": request_id,
          "runShape": "partial_plus_full",
          "linkedinUrl": "https://www.linkedin.com/in/janedoe",
          "email": "jane.doe@example.com",
      },
  )

  run = response.json()
  print(run["request_id"], run["status"])
  ```
</RequestExample>

<ResponseExample>
  ```json 202 theme={"dark"}
  {
    "request_id": "8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10",
    "run_id": "RUN_ID",
    "status": "started",
    "links": {
      "status": "/v2/developer/deep-search/8f6f2a1e-4b6d-4f3a-9d2c-1a7e5c3b9f10"
    }
  }
  ```
</ResponseExample>

## 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.

<ParamField body="requestId" type="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`.
</ParamField>

<ParamField body="profileId" type="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.
</ParamField>

<ParamField body="runShape" type="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](#run-shapes). Replaces the legacy `mode` and `partialProfile` fields. When `profileId` is provided, `candidate_discovery` is not valid.
</ParamField>

<ParamField body="structuredIntent" type="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](/api/search/structured-dsl).
</ParamField>

<ParamField body="email" type="string">
  Optional email address to use as an identity signal.
</ParamField>

<ParamField body="phoneNumber" type="string">
  Optional phone number to use as an identity signal. Send the field as `phoneNumber`, not `phone`.
</ParamField>

<ParamField body="linkedinUrl" type="string">
  Optional LinkedIn profile URL to use as an identity signal.
</ParamField>

<ParamField body="urls" type="string[]">
  Optional public `http(s)` URLs to use as identity signals.
</ParamField>

<ParamField body="usernames" type="string[]">
  Optional usernames to use as identity signals.
</ParamField>

<Note>
  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.
</Note>

## 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. |

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`](/api/search/smart-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:

```json 200 theme={"dark"}
{
  "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 `requestId` → `202` with `status: "started"` and a new run starts.
* Repeat request with the same `requestId` → `200` 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.

<ResponseField name="request_id" type="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](/api/search/deep-search-status) and as the idempotency key on retries.
</ResponseField>

<ResponseField name="run_id" type="string | null" required>
  An opaque tracking id for this run, or `null` when no workflow was started.
</ResponseField>

<ResponseField name="status" type="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.
</ResponseField>

<ResponseField name="links" type="object" required>
  `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.
</ResponseField>

<ResponseField name="result" type="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.
</ResponseField>

## 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 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}`](/api/search/deep-search-status) |

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](/api/search/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

<AccordionGroup>
  <Accordion title="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                                                                                                                                                                                                                                           |
  </Accordion>

  <Accordion title="402 - Insufficient credits">
    | Code                                 | Description                                                                                                      |
    | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
    | `developer_api_credits_insufficient` | Your remaining credits are lower than the run cost. The error includes `requiredCredits` and `remainingCredits`. |
  </Accordion>

  <Accordion title="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` |
  </Accordion>

  <Accordion title="429 - Rate limited">
    | Code                             | Description                                |
    | -------------------------------- | ------------------------------------------ |
    | `developer_api_key_rate_limited` | The API key exceeded the search rate limit |
  </Accordion>

  <Accordion title="502 - Upstream error">
    | Code                                   | Description                                                                  |
    | -------------------------------------- | ---------------------------------------------------------------------------- |
    | `developer_deep_search_request_failed` | Deep Search did not accept the request or could not be reached. Retry later. |
  </Accordion>

  <Accordion title="503 - Service unavailable">
    | Code                                            | Description                                        |
    | ----------------------------------------------- | -------------------------------------------------- |
    | `developer_deep_search_unavailable_for_request` | Deep Search is not available for this request.     |
    | `developer_deep_search_unavailable`             | Deep Search is currently unavailable. Retry later. |
    | `developer_deep_search_not_configured`          | Deep Search is not configured in this environment  |
  </Accordion>
</AccordionGroup>

## Start a run from a structured intent seed

<CodeGroup>
  ```bash curl theme={"dark"}
  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" }]
      }
    }'
  ```

  ```javascript JavaScript theme={"dark"}
  const response = await fetch("https://api.orbitsearch.com/v2/developer/deep-search", {
    method: "POST",
    headers: {
      "Authorization": "Bearer sk_orb_REDACTED",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      structuredIntent: {
        version: "v1",
        names: ["Jane Doe"],
        experiences: [{ organization: "Example Labs" }],
        semanticClauses: [{ text: "writes about developer tools" }],
      },
    }),
  });

  const run = await response.json();
  console.log(run.request_id, run.links.status);
  ```

  ```python Python theme={"dark"}
  import requests

  response = requests.post(
      "https://api.orbitsearch.com/v2/developer/deep-search",
      headers={
          "Authorization": "Bearer sk_orb_REDACTED",
          "Content-Type": "application/json",
      },
      json={
          "structuredIntent": {
              "version": "v1",
              "names": ["Jane Doe"],
              "experiences": [{"organization": "Example Labs"}],
              "semanticClauses": [{"text": "writes about developer tools"}],
          },
      },
  )

  run = response.json()
  print(run["request_id"], run["links"]["status"])
  ```
</CodeGroup>

## Discover candidate profiles from a structured intent

<CodeGroup>
  ```bash curl theme={"dark"}
  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" }
        ]
      }
    }'
  ```

  ```javascript JavaScript theme={"dark"}
  const requestId = crypto.randomUUID();

  const response = await fetch("https://api.orbitsearch.com/v2/developer/deep-search", {
    method: "POST",
    headers: {
      "Authorization": "Bearer sk_orb_REDACTED",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      requestId,
      runShape: "candidate_discovery",
      structuredIntent: {
        version: "v1",
        names: ["Sam Altman"],
        experiences: [{ organization: "OpenAI", title: "CEO", temporalScope: "current" }],
      },
    }),
  });

  const run = await response.json();
  console.log(run.request_id, run.links.status);
  ```

  ```python Python theme={"dark"}
  import requests
  import uuid

  request_id = str(uuid.uuid4())

  response = requests.post(
      "https://api.orbitsearch.com/v2/developer/deep-search",
      headers={
          "Authorization": "Bearer sk_orb_REDACTED",
          "Content-Type": "application/json",
      },
      json={
          "requestId": request_id,
          "runShape": "candidate_discovery",
          "structuredIntent": {
              "version": "v1",
              "names": ["Sam Altman"],
              "experiences": [
                  {"organization": "OpenAI", "title": "CEO", "temporalScope": "current"}
              ],
          },
      },
  )

  run = response.json()
  print(run["request_id"], run["links"]["status"])
  ```
</CodeGroup>
