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

# Migrate to v3 Search and Enrich

> Replace legacy people-search, Deep Search, profile-read, and workflow integrations with the v3 Search and Enrich APIs.

v3 reduces the people API to two concepts:

* **Search** finds people from plain-English queries, structured criteria, or identity signals, optionally discovers additional candidates, and builds every result to a requested depth.
* **Enrich** operates on an existing profile by canonical Orbit ID or profile slug: return a partial profile, return a full profile, or regenerate a fresh full profile.

This migration changes request shapes and response lifecycles. Treat it as a client-contract migration, not an endpoint rename.

## Endpoint map

| Legacy integration                                        | v3 replacement                                       | Main change                                                                                            |
| --------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `POST /v2/developer/search`                               | `POST /v3/search`, then `GET /v3/search/{search_id}` | Keep the raw `query`; rename controls and poll for completion.                                         |
| Search SSE                                                | `POST /v3/search`, then poll                         | v3 has no SSE endpoint.                                                                                |
| `POST /v2/developer/search/workflows`                     | `POST /v3/search`                                    | Replace stage configuration with three independent knobs.                                              |
| Deep Search using identity signals or candidate discovery | `POST /v3/search`                                    | Move identity values under `signals`.                                                                  |
| Deep Search using `profileId`                             | `POST /v3/enrich/{profile_id}`                       | Pass the canonical Orbit ID or profile slug, then choose `partial`, `full`, or `regenerate`.           |
| Deep Search status                                        | Search status or Enrich status                       | Poll the resource that matches the original objective.                                                 |
| Profile read                                              | `GET /v3/enrich/{profile_id}`                        | Read directly by canonical Orbit ID, alias, or profile slug. The response uses the v3 profile wrapper. |

## Migrate Search

### Before

```json theme={"dark"}
{
  "query": "machine learning engineers at Anthropic in San Francisco",
  "numUsers": 10,
  "includeProfile": true,
  "includeMatchReason": false
}
```

### After

```json theme={"dark"}
{
  "request_id": "search-ml-anthropic-sf",
  "query": "machine learning engineers at Anthropic in San Francisco",
  "candidate_discovery": false,
  "profile_depth": "partial",
  "include_profile": true,
  "limit": 10
}
```

v3 keeps the raw `query` field and removes match reasons. Orbit sends plain-English queries through its normal model router and derives structured intent internally when candidate discovery needs it.

If your application already has exact filters, send `intent` alone. You can also keep `query` and add `intent` when the plain-English request provides useful context but exact fields must take precedence:

| Query concept                      | v3 intent field   |
| ---------------------------------- | ----------------- |
| Person name                        | `names`           |
| Job title, employer, or work dates | `experiences`     |
| School or education dates          | `schools`         |
| Location                           | `geo`             |
| Age, birth year, or gender         | `demographics`    |
| Expertise or descriptive criteria  | `semanticClauses` |

When both are present, Orbit derives a base intent from `query` and overlays your `intent`. At each supplied field, arrays, scalar values, and `null` replace the derived value; nested objects merge field by field. Known-profile Search uses the original query plus the merged intent. Candidate Discovery uses the merged intent. `query`, `intent`, or both can be combined with identity `signals`.

### Replace workflow stages with three knobs

| Objective                                               | v3 field                               |
| ------------------------------------------------------- | -------------------------------------- |
| Search known people only or also find additional people | `candidate_discovery: false` or `true` |
| Build each selected result to a useful or full profile  | `profile_depth: partial` or `full`     |
| Embed profiles or return IDs/status only                | `include_profile: true` or `false`     |

Known search always runs. `limit` caps existing Orbit index matches and accepts values from 1–100. Candidate Discovery results append outside that quota. `candidate_discovery_limit` defaults to 10 and accepts values from 1–50. A lower value reduces downstream profile-generation fanout, although source discovery and clustering can still dominate initial discovery time. `include_profile` changes only the response; it does not skip profile building.

### Move identity signals

```json theme={"dark"}
{
  "request_id": "identity-person-example",
  "signals": {
    "email": "person@example.com",
    "usernames": ["person_example"],
    "linkedin_url": "https://www.linkedin.com/in/person-example"
  },
  "candidate_discovery": true,
  "profile_depth": "partial",
  "include_profile": true,
  "limit": 1
}
```

Field renames:

| Legacy field                         | v3 field                                                                                  |
| ------------------------------------ | ----------------------------------------------------------------------------------------- |
| `requestId`                          | `request_id`                                                                              |
| `structuredIntent`                   | `intent`                                                                                  |
| `numUsers` or the Search step limit  | `limit`                                                                                   |
| Candidate Discovery step limit       | `candidate_discovery_limit`. Defaults to `10`; set it only when you need a different cap. |
| Profile Upgrade step limit           | No direct equivalent. `profile_depth` controls required profile upgrades.                 |
| `includeProfile` / `includeProfiles` | `include_profile`                                                                         |
| `email`                              | `signals.email`                                                                           |
| `phoneNumber`                        | `signals.phone`                                                                           |
| `linkedinUrl`                        | `signals.linkedin_url`                                                                    |
| `urls`                               | `signals.urls`                                                                            |
| `usernames`                          | `signals.usernames`                                                                       |

Remove `structuredIntent.version`; Orbit owns it. With `candidate_discovery: true`, address, email, and phone signals can each return multiple associated people. Profile IDs do not belong in Search.

## Migrate profile upgrades to Enrich

| Legacy run shape       | v3 operation |
| ---------------------- | ------------ |
| `partial_only`         | `partial`    |
| `partial_plus_full`    | `full`       |
| `full_generation_only` | `full`       |
| `full_regeneration`    | `regenerate` |

```bash theme={"dark"}
curl -X POST "https://api.orbitsearch.com/v3/enrich/profile_123" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "regenerate",
    "include_profile": true
  }'
```

`regenerate` builds toward full for level 1 and 2 profiles. For an existing level 3 profile, it forces a fresh full build.

## Migrate profile reads

Read the current public profile directly by Orbit profile ID, known alias ID, or
public profile slug. An Enrich `request_id` is not required.

```http theme={"dark"}
GET /v3/enrich/profile_123
```

The response returns the canonical profile ID, generation level, and complete
v3 developer profile object. Follow an Enrich response's `links.profile` value
when one is available. See [Read a profile](/api/enrich/read-profile) for the
response schema.

Profile read is read-only. To build a level 1 profile to level 2, call
`POST /v3/enrich/{profile_id}` with `operation: partial`. The POST can return
`202 Accepted` with `status: running`. Poll `links.status` until the operation
is `completed` or `failed`. It returns `200 OK` immediately when the existing
profile already satisfies the operation.

The response names the canonical Orbit ID `profile_id`; that is the API field name, not a second ID type.

## Update lifecycle handling

* A Search POST can return `202` with results already becoming ready. Poll `links.status` until `completed`, `completed_with_errors`, or `failed`.
* An Enrich POST can return `200` immediately when the existing profile satisfies the operation, or `202` when work is running.
* Treat `completed_with_errors` as usable partial success and inspect each result.
* Batch Enrich has no aggregate GET. Poll each running child using its own `links.status`.
* Persist the returned `request_id` for status polling. If you send an `Idempotency-Key` header, reuse it only for the same logical request.

## Update scopes and billing assumptions

* Search and Enrich start/status require `search:read`.
* Any request that embeds profiles also requires `profile:read`.
* Direct Enrich profile reads require `profile:read`.
* v3 Search and Enrich do not consume credits. Continue handling `429` rate limits.

## Cutover checklist

* Keep free-form people searches in `query`. Use [structured intent](/api/search/search-intent) alone for fully structured requests, or add it to `query` when exact fields must override Orbit's interpretation.
* Remove legacy fields and internal workflow terminology from requests.
* Cap `limit` at 100.
* Store returned `request_id` and `search_id` values where applicable.
* Poll the correct status endpoint with backoff and jitter.
* Handle `completed_with_errors` separately from `failed`.
* Read profiles by canonical Orbit ID or slug, or follow the returned Enrich `links.profile` URL.
* Run old and new clients side by side on representative inputs before switching production traffic.
* Log v3 IDs and error codes so support can trace a request without its API key.

See [Error handling](/guides/v3-error-handling) and [Rate limits](/concepts/rate-limits) before production cutover.
