Jobs: define once, reference many
The Jobs catalog stores a job's job_description (prose) and its structured
requirements config once, so CV DeepMatch and CV DeepSearch runs can
reference it instead of re-sending the full content every time.
The model
A saved job has:
| Field | What it is |
|---|---|
position_id | Your own job identifier ([A-Za-z0-9._-], 1–28 chars) — the same position_id the run APIs use. Optional; unique per client among active jobs. |
id (aka job_id) | The platform-assigned UUID, returned on create. An alternative reference. |
job_description | The JD prose (non-empty string). |
config | The structured requirements config — strictly validated on every save with the same contract CV DeepMatch enforces at submit (Position config reference). Must not contain a job_description key. |
One job serves both services. CV DeepMatch runs directly on
job_description + config. CV DeepSearch composes its single
position_metadata blob from the same job:
{ ...config, "job_description": "<the JD prose>" }
Create a job, then reference it from both services
# 1. Define once
curl -X POST https://platform.zenhire.ai/api/v1/jobs \
-H "X-API-Key: $ZENHIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"position_id": "eng-backend-2026",
"job_description": "Senior backend engineer with 5+ years of Node.js…",
"config": {
"name": "Senior Backend Engineer",
"requirements": {
"workExperience": {
"from": 5, "to": 10, "importance": 5, "time_importance": 3,
"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 }
}
}
}
}
}'
# → 201 { "id": "6f0d5c2e-…", "position_id": "eng-backend-2026", … }
# 2a. Match a CV against it — no job_description / config needed
curl -X POST https://platform.zenhire.ai/api/v1/cvdeepmatch/submit \
-H "X-API-Key: $ZENHIRE_API_KEY" \
-F "cv_file=@candidate.pdf" \
-F "position_id=eng-backend-2026"
# 2b. Search a corpus for it — no position_metadata needed
curl -X POST https://platform.zenhire.ai/api/v1/cvds/search \
-H "X-API-Key: $ZENHIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "corpus_id": "acme-eng-pool", "position_id": "eng-backend-2026" }'
Both runs record the resolved job in their responses' job_id field
(null for pure inline runs), so you can trace every run back to the job it
executed against.
Referencing rules (both services)
-
At least one of
position_id/job_idis required onPOST /api/v1/cvdeepmatch/submitandPOST /api/v1/cvds/search. -
When both are sent they must resolve to the same saved job (
400 INVALID_INPUTotherwise);job_idwins for resolution. -
A reference with no inline content that resolves to no saved job for your client returns
400 JOB_NOT_FOUND:{"error": {"code": "JOB_NOT_FOUND","message": "No saved job with this position_id/job_id for this client. Provide both config and job_description, or create the job first via POST /api/v1/jobs."}}Fix: send the inline content, or create the job first (
POST /api/v1/jobs), or check the reference for typos. Archived jobs no longer resolve.
CV DeepMatch: job_description + config are both-or-neither
Send both to run with inline content (exactly as before), or neither
to run with the referenced job's stored content. Sending exactly one returns
400 INVALID_INPUT — there is no partial merge with stored fields. To change
one field of a saved job, use PATCH /api/v1/jobs/{jobId}, never the run
endpoint.
CV DeepSearch: position_metadata is optional with a reference
Omit position_metadata and the search composes it from the saved job. Send
it inline and the run uses your blob verbatim (the reference, if it resolves,
still populates the run's job_id attribution).
Saving from a run: saveJob: true
Both submit endpoints accept an optional saveJob flag that writes the
inline content to the catalog before running:
- an existing job under the reference is updated; otherwise a new job is
created under
position_id. Ajob_idalone can only update — it never creates (400 INVALID_INPUT: "Cannot create a job with a system id; use position_id."). - CV DeepMatch saves the inline
job_description+config— already contract-validated for the run itself. - CV DeepSearch saves the inline
position_metadataonly when it is genuinely a job: the blob is decomposed — its reservedjob_descriptionkey (a non-empty string) becomes the JD prose, the remainder becomes the config — and the config is strictly validated with the save-time contract. On any failure the search returns400 INVALID_INPUTwith per-field detail inerror.details.fields, nothing is saved and no run is started (all-or-nothing). Inline searches withoutsaveJobremain unvalidated, exactly as before.
Automatic job_updated on CV DeepSearch
CV DeepSearch caches a search plan per position and rebuilds it when
job_updated: true.
For a run whose content comes from a saved job (a reference-only search,
or a saveJob that just updated the job), the platform now derives that
flag automatically: it is true exactly when the job changed since the
last search of that job against the same corpus (or when it is the first
such search). The client-sent job_updated is ignored for those runs —
you never need to track plan staleness for saved jobs.
Escape hatch: an inline (unsaved) search keeps the client-sent
job_updated exactly as before — the platform cannot know whether ad-hoc
content differs from the cached plan.
The run's detail response reports the effective flag in is_job_updated.
Updating and archiving
PATCH /api/v1/jobs/{jobId}— partial update; every provided field is re-validated with the strict save-time rules. Past runs are never altered (each run snapshots the content it executed with); the next search against the updated job rebuilds its plan automatically.DELETE /api/v1/jobs/{jobId}— archive (soft delete): the job leaves list/lookup, run submits can no longer reference it, and itsposition_idbecomes free for reuse.204on success.position_idis unique per client among active jobs — a duplicate returns409 POSITION_ID_CONFLICT.
Reference
- Jobs API reference — full endpoint + schema contract.
- Position config reference — the
field-by-field
configcontract enforced at save time. - CV DeepMatch guide · CV DeepSearch search guide