Search for people
Last updated:
Search for people with a natural-language query — or companies, by sending target: company.
/v1/searchAuthenticated with your x-api-key. See Authentication.
This endpoint searches people by default. To search companies instead, send target: company — see Search for companies below.
Request
POST /v1/search
Host: search-api.xverum.com
x-api-key: XVERUM-API-KEY
Content-Type: application/jsonBody
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
query | string | yes | 1–500 chars, trimmed, must not be blank | Natural-language search query |
page | integer | no | ≥ 1, default 1 | 1-based page number |
page_size | integer | no | 1–100, default 10 | Results per page |
target | string | no | people or company, default people | Which entity to return. company returns companies (see Search for companies); no automatic people-vs-company detection is done |
Example body
{
"query": "senior Python developers with Kubernetes in Seattle",
"page": 1,
"page_size": 10
}Pagination
Offset-based:
page— integer,≥ 1(default1).page_size— integer, `1` to `100` (default10).page * page_sizemust not exceed 1000 — the max result window.
A page_size outside 1–100 returns 422 validation_error; a page * page_size over 1000 returns 400 invalid_pagination. Deep pagination over the entire corpus is intentionally unsupported — refine the query instead.
total_count is an estimate of total matches, not an exact count. Use it as a rough size signal, not for precise "page N of M" math.
Example
curl -X POST https://search-api.xverum.com/v1/search \
-H "x-api-key: XVERUM-API-KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "senior Python developers with Kubernetes in Seattle"
}'Response — 200 OK
| Field | Type | Description |
|---|---|---|
result_type | string | people, or company when you send target: company — discriminates the shape of results |
results | Profile[] | Matched profiles (see schema below) |
total_count | integer | Estimated total number of matches |
page | integer | 1-based page number echoed back |
page_size | integer | Results per page echoed back |
credits_used | integer | Credits deducted on this call (1 per returned result) |
credits_remaining | integer | Credits left after this call — a best-effort fresh read from billing; on a transient billing outage it falls back to the balance seen at auth time |
request_id | string | Correlation id — also returned in the X-Request-Id header |
Every response also carries X-Request-Id and the X-RateLimit-* headers (see Rate limits).
To get the complete profile — employment history, education and profile summary — pass a result's id to GET /v1/profiles/{id}.
Profile schema
| Field | Type | Description |
|---|---|---|
id | string | Opaque profile id, stable across sessions and queries under the current scheme. Pass to GET /v1/profiles/{id} for the full profile, or to GET /v1/profiles/{id}/job-change for the Next Move Signal score. Store it — any id you already hold stays fetchable. |
social_url | string | null | Person social URL; null when no public profile URL is on record for the person |
full_name | string | Full name |
headline | string | null | Person social headline or summary; null when the person has not written one |
location | string | null | Location string; null when no location is on record for the person |
company_name | string | null | Most recent employer; null when no current employer is held for the person |
position | string | null | Most recent job title; null when no current role is held for the person |
industry | string | null | Industry vertical, taken from the person's current employer; null when that employer, or its industry, is not known |
evidence_summary | string | How recently this profile was verified, from its refresh date — Verified last 30 days through Verified over 120 days ago |
Example response
{
"result_type": "people",
"results": [
{
"id": "a1b2c3d4",
"social_url": "https://www.linkedin.com/in/jane-doe-sea",
"full_name": "Jane Doe",
"headline": "Senior Python Engineer at Acme",
"location": "Seattle, WA",
"company_name": "Acme",
"position": "Senior Python Engineer",
"industry": "software",
"evidence_summary": "Verified last 30 days"
},
…
],
"total_count": 128,
"page": 1,
"page_size": 10,
"credits_used": 10,
"credits_remaining": 4990,
"request_id": "8b1d4f0c12a44b7a"
}Billing
Each profile returned in results consumes 1 credit from your account, echoed back as credits_used; credits_remaining shows the balance left. A request that returns 10 profiles spends 10 credits.
credits_remaining is read fresh from billing on each successful call and is best-effort: if billing is briefly unavailable it falls back to the balance seen at authentication. Treat it as an indicative post-call balance, not a settled ledger figure.
Search for companies
Send target: "company" on this same POST /v1/search endpoint to search companies instead of people. query, page and page_size behave exactly as above, and pagination and the 1000 max result window are the same.
Example body
{
"query": "fintech companies in London with 50–200 employees",
"page": 1,
"page_size": 10,
"target": "company"
}The response envelope is identical to the people response, except result_type is company and each item in results is a company card.
Company schema
| Field | Type | Description |
|---|---|---|
id | string | Opaque company id. Pass to GET /v1/companies/{company_id} for the full company record. Store it — any id you already hold stays fetchable. |
social_url | string | null | Company social URL; null when none is on record for the company |
company_name | string | null | Company name; null when no name is on record for the company |
slogan | string | null | Short tagline; null when none is on record |
headquarters | string | null | Headquarters as City, Region; null when no location is on record |
country_code | string | null | Lowercase country code of the headquarters; null when not known |
industry | string | null | Industry vertical; null when not known |
employees_num | integer | null | Employee count; null when not known |
website | string | null | Company website; null when none is on record |
founded | integer | null | Year the company was founded; null when not known |
evidence_summary | string | How recently this company record was verified, from its refresh date — Verified last 30 days through Verified over 120 days ago |
Company results are billed exactly like people results — 1 credit per company returned, echoed back as credits_used.
To get a company's full record — description, specialties, office locations, organisation type, follower count and funding — pass a result's id to GET /v1/companies/{company_id}.
Errors
All /v1 errors use the structured envelope (see Authentication):
| Status | error.code | Condition |
|---|---|---|
400 | off_topic_query | Query is not a Xverum data query |
400 | invalid_pagination | page * page_size exceeds the 1000 max result window |
401 | invalid_api_key | Missing or invalid API key |
402 | out_of_credits | Out of credits — the envelope carries an upgrade_url to top up |
403 | account_not_authorized | Account not authorized |
422 | validation_error | Body failed validation (e.g. query blank or longer than 500 chars) |
429 | rate_limited | Rate limit exceeded — see Rate limits |
502 | upstream_unavailable | An upstream dependency failed — retry shortly |
503 | search_unavailable | Search is temporarily saturated or unreachable — retry shortly |
503 | auth_unavailable | Authentication is briefly unreachable — retry shortly |
503 | classification_degraded | Query understanding is degraded — retry shortly |
{
"error": {
"code": "off_topic_query",
"message": "Query is not a Xverum data query.",
"request_id": "8b1d4f0c12a44b7a"
}
}The message is advisory and target-specific — match on error.code, not its text. A target: "company" search that is off topic returns a different message:
{
"error": {
"code": "off_topic_query",
"message": "Query is not a company search. If you were looking for people, retry with target=people.",
"request_id": "8b1d4f0c12a44b7a"
}
}A company search cannot fall back to people, so this points a mis-targeted request at the right target rather than guessing which half to answer.