Use structuredIntent when your product already knows the search filters it wants to apply. For POST /v2/developer/search, Orbit still requires a human-readable query; the structured intent is used as the search plan instead of asking Orbit to infer every clause from the query text. For Deep Search, the same structured intent envelope is one of the identity signals that can seed an async profile generation run.
Structured intent is supported on these developer API surfaces:
POST /v2/developer/search
POST /v2/developer/search/sse
POST /v2/developer/deep-search
The same search request body works for global search and directory-scoped search. The API key must have search:read.
Contract Invariants
| Surface | Required fields | Structured intent behavior |
|---|
| Search | query, numUsers | structuredIntent is optional. When present, query still must describe the same user intent in plain English. |
| Search SSE | query, numUsers | Same request body as Search. |
| Deep Search | At least one identity signal: structuredIntent, email, phoneNumber, linkedinUrl, urls, or usernames | structuredIntent is an optional identity seed for the run; personalization is rejected. Legacy top-level fields (mode, partialProfile, dry_run, options) are rejected with replacement hints. |
structuredIntent.version is required and must be "v1". The DSL fields are flat under structuredIntent; do not wrap them in an extra intent object.
entityClauses are not part of the public DSL yet. Do not send entity-resolution internals such as resolvedCompanyFilters, resolvedSchoolFilters, resolvedEntityFilters, or entityClauses; the server rejects those fields.
Search shape
{
"query": "current OpenAI engineers in San Francisco with infrastructure experience",
"numUsers": 10,
"structuredIntent": {
"version": "v1",
"experiences": [
{
"organization": "OpenAI",
"title": "engineer",
"temporalScope": "current"
}
],
"geo": {
"place": "San Francisco",
"distance": "50km"
},
"semanticClauses": [
{
"text": "infrastructure"
}
]
}
}
query remains required even with structured intent. It is used for moderation, logging, summaries, match reasons, and search-quality training. Do not duplicate every structured clause into prose; the query should describe the same user intent in plain English.
Deep Search Shape
Deep Search accepts the same structuredIntent object on POST /v2/developer/deep-search as one of its identity signals, alongside email, phoneNumber, linkedinUrl, urls, and usernames. At least one identity signal is required; structuredIntent can stand alone or be combined with the other signals.
{
"runShape": "partial_plus_full",
"linkedinUrl": "https://www.linkedin.com/in/janedoe",
"email": "jane.doe@example.com"
}
Structured intent can also seed a run on its own, or sharpen another seed with a name and matching signals:
{
"urls": ["https://github.com/janedoe"],
"structuredIntent": {
"version": "v1",
"names": ["Jane Doe"],
"experiences": [{ "organization": "Example Labs" }],
"demographics": { "ageRange": { "min": 23, "max": 25 } }
}
}
Deep Search maps structuredIntent.names[0] to the person name; experiences, schools, semantic clauses, location, and demographic fields are matching signals for the run.
Deep Search rejects unknown top-level fields. The legacy mode, partialProfile, dry_run, and options fields return developer_deep_search_field_unsupported with a replacement hint; run behavior is controlled by runShape instead. See Deep Search for the full request contract.
Deep Search does not have the synchronous search endpoint’s API-key user network or location context, so use POST /v2/developer/search for strict personalization.network or personalization.nearMe behavior.
Structured Intent Fields
| Field | Type | Required | Description |
|---|
structuredIntent.version | string | Yes | Must be "v1" |
structuredIntent.names | string[] | Deep Search only | Person names to match. names[0] is treated as the person name |
structuredIntent.experiences | object[] | No | Work history filters |
structuredIntent.schools | object[] | No | Education filters |
structuredIntent.semanticClauses | object[] | No | Text concepts to match against profile evidence |
structuredIntent.geo | object | No | Current or historical location filter |
structuredIntent.demographics | object | No | Age, birth year, or gender filters |
structuredIntent.personalization | object | Search only | Search relative to the API key user’s network or location |
Experience clauses
{
"experiences": [
{
"organization": "OpenAI",
"title": "engineer",
"titleAnyOf": ["engineer", "researcher"],
"year": 2024,
"startYear": 2020,
"startYearLte": 2021,
"endYear": 2024,
"temporalScope": "current"
}
]
}
| Field | Type | Description |
|---|
organization | string | Company or organization name |
title | string | Role title |
titleAnyOf | string[] | Alternative role titles |
year | number | A year that should overlap the experience |
startYear | number | Exact start year |
startYearLte | number | Start year must be less than or equal to this value |
endYear | number | Exact end year |
temporalScope | string | "current", "historical", or "both" |
School clauses
{
"schools": [
{
"school": "Stanford",
"schoolAnyOf": ["Stanford University"],
"graduationYear": 2014,
"startYear": 2010,
"endYear": 2014,
"temporalScope": "historical"
}
]
}
| Field | Type | Description |
|---|
school | string | School name |
schoolAnyOf | string[] | Alternative school names |
graduationYear | number | Graduation year |
startYear | number | Education start year |
endYear | number | Education end year |
temporalScope | string | "current", "historical", or "both" |
relation | string | Optional advanced relation label |
Semantic clauses
{
"semanticClauses": [
{
"text": "machine learning infrastructure"
}
]
}
Use text for the main profile evidence concept. Add anyOf only when aliases or sibling phrases should satisfy that same concept:
{
"semanticClauses": [
{
"text": "machine learning infrastructure",
"anyOf": ["distributed training", "model serving"]
}
]
}
When both are set, Search treats them as one semantic group: text is the canonical phrase, and anyOf adds alternatives for the same clause. Deep Search treats both the text phrase and each anyOf phrase as matching signals for the run.
Location and demographics
{
"geo": {
"place": "San Francisco",
"distance": "50km",
"isHistorical": false
},
"demographics": {
"ageRange": { "min": 30, "max": 45 },
"birthYearRange": { "min": 1980, "max": 1995 },
"gender": "female"
}
}
ageRange and birthYearRange require both min and max, and min must be less than or equal to max.
Personalization
{
"personalization": {
"network": { "scope": "first_degree" },
"nearMe": { "distance": "25km" }
}
}
network.scope currently supports "first_degree". nearMe uses the API key user’s location context when available.
Streaming
POST /v2/developer/search/sse accepts the same body as synchronous Search:
curl -N -X POST "https://api.orbitsearch.com/v2/developer/search/sse" \
-H "Authorization: Bearer sk_orb_REDACTED" \
-H "Idempotency-Key: request-uuid-or-client-retry-key" \
-H "Content-Type: application/json" \
-d '{
"query": "current OpenAI engineers with infrastructure experience",
"numUsers": 10,
"includeMatchReason": true,
"structuredIntent": {
"version": "v1",
"experiences": [{ "organization": "OpenAI", "title": "engineer", "temporalScope": "current" }],
"semanticClauses": [{ "text": "infrastructure" }]
}
}'
The first initial event contains the same user payload returned by synchronous Search. If includeMatchReason is true, additional update events can add match reasons for top results.