CV DeepSearch — Search (query the corpus)
Search is the second of the two CV DeepSearch flows: once you've filled a corpus, you run repeated searches against it for different positions. This guide covers triggering a search and consuming the run.
For the concepts, see the CV DeepSearch overview. The full endpoint reference is the Search group in the CV DeepSearch API reference sidebar.
Prerequisites
- A corpus with at least one
embeddedcandidate — see the Ingestion guide. - An API key with the
cvdeepsearchpermission. - (Optional) A publicly-reachable HTTPS endpoint, only if you want the completion webhook.
Run a search
POST https://platform.zenhire.ai/api/v1/cvds/search
curl -X POST "https://platform.zenhire.ai/api/v1/cvds/search" \
-H "X-API-Key: zh_api_…" \
-H "Content-Type: application/json" \
-d '{
"corpus_id": "acme-eng-pool",
"position_id": "eng-backend-2026",
"position_metadata": {
"name": "Senior Backend Engineer",
"requirements": {
"workExperience": { "from": 5, "to": 10, "importance": 5, "relevant_industries": ["SaaS"], "industries_config": [{ "name": "SaaS", "importance": 5 }] },
"skills": { "importance": 5, "skills_config": { "hard_skills": [{ "name": "Node.js", "importance": 5 }], "requirements": { "minimal_qualifications": true, "preferable_qualifications": true } } }
}
},
"top_n": 50,
"webhook_url": "https://example.com/cvds/callback"
}'
Response (HTTP 200):
{ "id": "cvds_8mK2pQ7rT1aB9xY3wZ0vNn", "status": "pending" }
Save the id — you poll it (or correlate the webhook) to get the result.
The position_metadata config
position_metadata is the same shape CV DeepMatch consumes as its
requirements config. The API requires only that it be a non-empty object;
the inner shape is the search engine's contract. The full field-by-field
contract — the 1–5 importance scale, where each requirement belongs, education
ON/OFF, minimal vs preferable qualifications, the language-config quirks, and
the cardinality limits — is in the
Position config reference, with the
per-field schema at
CvDeepMatchRequirements.
job_updated & plan caching
The search planner caches a plan per position_id. Set job_updated: true only
when the role's requirements changed since the last search (it rebuilds the
plan); leave it false (default) to reuse the cached plan and re-rank.
top_n
top_n caps how many ranked candidates you want back. The server enforces an
upper bound; values above the cap are clamped.
Idempotency
Pass an idempotency_key to make the trigger safe to retry — the same key
replays the existing run id.
Consume the results
Two ways: poll (always available), or wait for the webhook (only if you
supplied a webhook_url).
Option A — Poll
GET https://platform.zenhire.ai/api/v1/cvds/runs/{id}
curl "https://platform.zenhire.ai/api/v1/cvds/runs/cvds_8mK2pQ7rT1aB9xY3wZ0vNn" \
-H "X-API-Key: zh_api_…"
{
"id": "cvds_8mK2pQ7rT1aB9xY3wZ0vNn",
"status": "pending",
"corpus_id": "acme-eng-pool",
"position_id": "eng-backend-2026",
"is_job_updated": false,
"top_n": 50,
"source": "requests",
"created_at": "2026-06-09T10:00:00.000Z",
"results": null,
"results_pending": true
}
When results land, the candidate list is returned best-first — an ordered
ranking, not a percentage match. Treat each result's score as a sort key, not
a 0–100% score.
The best-first candidate list, the GET /api/v1/cvds/runs/{id}/results
pagination endpoint, and the final webhook completion payload are finalized in a
follow-up release. Until then the run poll returns the run's status with
results: null and results_pending: true. The shapes below describe the
provisional completion envelope; treat them as a forward reference, not a
final contract.
Option B — Webhook callback (optional)
If you supplied a webhook_url, we POST a signed callback when the run
finishes. The signing + retry mechanism is identical to CV DeepMatch: an
HMAC-SHA256 signature in the X-CVDM-Signature header (t=<unix-ts>,v1=<hex>,
Stripe-style), up to 3 attempts with exponential backoff, retried on 5xx / 429 /
network errors.
You can register a webhook endpoint and send yourself a test cvdeepsearch
completion event today from the self-serve webhook tool in your dashboard,
even before the live completion payload is finalized — pick the
cvdeepsearch.completed event type and hit Test.
Provisional completion payload shape (subject to change when results land — modeled on the CV DeepMatch completion callback):
{
"event": "cvdeepsearch.completed",
"id": "cvds_8mK2pQ7rT1aB9xY3wZ0vNn",
"corpus_id": "acme-eng-pool",
"position_id": "eng-backend-2026",
"status": "completed",
"completed_at": "2026-06-09T10:02:00.000Z"
}
Verifying the signature — use the same verifier as CV DeepMatch (the
X-CVDM-Signature header has the form t=<unix-ts>,v1=<hex>, where <hex> is
the lower-case hex HMAC-SHA256 of <unix-ts>.<compact-JSON-body>). See the
CV DeepMatch verification snippet
for the Node.js and Python implementations.
Error codes
The search endpoints use the shared standard error envelope:
error.code | HTTP | Meaning | Retry? |
|---|---|---|---|
INVALID_INPUT | 400 | A field failed validation. | After fix |
MISSING_CORPUS_ID | 400 | A mandatory corpus_id was missing (fail-closed). | After fix |
INSECURE_WEBHOOK_URL | 400 | webhook_url is http://. Use HTTPS. | After fix |
PRIVATE_WEBHOOK_URL | 400 | webhook_url resolves to a private / loopback address. | After fix |
MISSING_PERMISSION | 401 / 403 | Missing/invalid key, or missing cvdeepsearch perm. | Contact support |
NOT_FOUND | 404 | No run with that id for your client. | No |
STEP_FUNCTIONS_FAILED | 502 | Downstream search pipeline failed to start. | Yes, same idempotency_key |
INTERNAL_ERROR | 500 | Uncategorised server error. | After delay |
Next steps
- Read the Position config reference for
how to shape
position_metadata. - Browse the full CV DeepSearch API reference for every parameter, schema, and example.
- For interactive exploration, use the Try it console on each endpoint reference page, or the API Sandbox in your dashboard.