Assessments
Creating, shaping and reading an assessment.
An assessment is one candidate against one role. It is either a resume_screen
or an ai_interview, and both are the same resource to you.
Shaping the interview
Three things are yours to set, and none of them needs a round configured up front.
Length. duration_minutes, 5 to 120. This is a real lever, not a label: it
decides how long the interviewer stays on and how many questions it gets through.
Omit it and the round type's own default applies.
Shape. round_type picks the kind of conversation, for example
general_interview, hr_round or system_design. GET /v1/round-types lists
what your organisation can use and each one's default length.
Questions. questions is asked in place of generated ones. The first becomes
the opening question and the rest form the pool the interviewer draws from.
The interviewer samples that pool by length rather than asking every entry: roughly two questions on a 16-30 minute assessment, three up to 65, four otherwise. A list of ten on a fifteen-minute call is ten candidates for four slots, not a script.
Omit questions and we generate them from the role. That generation runs
after the create returns, because it involves a web search and a model call.
If the candidate opens the link before generation lands, the interview opens on
a generic fallback for the round rather than a real question. On a coding
round that fallback is the bare sentence "Write a function that solves the
following problem", with no problem behind it, and main_question on the report
says the same.
The interviewer notices and rebuilds while it is still greeting, so a small gap
usually corrects itself, and the generated set is reused for every later
assessment with the same settings. Passing questions skips generation
entirely, which is the only way to remove the timing question altogether.
Inviting the candidate
Set send_invite: true on the create and we email the candidate a link to the hosted
booking page, where they pick a time and then join. Off by default, so an integration
that already emails its own candidates does not start sending twice.
The mail goes from your own domain once a sending domain is verified in Settings, otherwise from ours, and carries your logo and brand colour. It is skipped in sandbox and when the candidate has no email address.
The booking page is hosted on your branded domain when you have one configured, so the candidate never sees ours. It is also the answer to "do we have to build a scheduler": usually not.
If you would rather run the whole candidate journey yourself, leave send_invite off and
use POST /v1/assessments/{id}/launch-link. That link skips booking entirely and drops
the candidate straight into the assessment.
Repeat creates are cheap
Behind the scenes each distinct configuration gets one interview round, reused for every candidate you send with the same settings. Changing the length, the shape or the questions makes a new one. You never see this, and you never pay for question generation twice for the same setup.
Resume screens are not interviews
A resume_screen has no call, no length and no questions. It scores the
candidate's resume against the role. It comes back as the same assessment
resource with the same statuses, so one code path handles both.
Two things it does need that an interview does not.
The job needs skills. Coverage and the weighted score are computed against
the job's skill list, so a screen on a job without one is refused with
job_skills_required. Send them when you create the job:
{
"title": "Backend Engineer",
"skills": [
{ "name": "Go", "skill_type": "must_have", "weightage": 0.6 },
{ "name": "PostgreSQL", "skill_type": "must_have", "weightage": 0.4 },
{ "name": "Kubernetes", "skill_type": "good_to_have" }
]
}
You have to send us the resume. Creating the assessment does not upload one. It is two calls, because the file goes straight to storage and never passes through this API:
# 1. Ask for a URL. PDF, DOC or DOCX.
curl -X POST https://hiring-api.experthire.cloud/v1/assessments/$ID/resume/upload-url \
-H "Authorization: Bearer $EH_KEY" \
-d '{"filename": "jane-doe.pdf"}'
# 2. PUT the file to the returned upload_url with the returned content_type,
# then hand back the key.
curl -X POST https://hiring-api.experthire.cloud/v1/assessments/$ID/resume \
-H "Authorization: Bearer $EH_KEY" \
-d '{"key": "resumes/<assessment-id>/a1b2c3d4e5.pdf"}'
Still a placeholder: $EH_KEY. Add it under Your values above.
That returns 202 and scoring runs in the background. Poll
GET /v1/assessments/{id}/resume for processing, scored or failed. A
failed result carries an error_code and puts the assessment back to
scheduled, so you can upload a different file.
In sandbox this writes a fixed result straight away without running the scorer, so the report you parse has the same shape a live one does.
Status
A list response wraps the rows and carries an opaque cursor.
{
"object": "list",
"data": [
{
"object": "assessment",
"id": "3b8e1d02-5c77-4c2a-8a41-9b2f7e6d4c10",
"type": "ai_interview",
"status": "completed",
"job_id": "9c1f0b7e-2f4a-4a51-9a2e-6f0d5b3c1a88",
"candidate_id": "7e2a94c1-0f3b-4d88-b6a2-1c5e8f0a9d33",
"score": 68,
"livemode": true,
"created_at": 1767225601,
"updated_at": 1767229200
}
],
"has_more": true,
"next_cursor": "c_b2ZmXzIw",
"total_count": 143
}
| Status | Meaning |
|---|---|
pending | Created, not yet scheduled |
scheduled | Ready for the candidate |
in_progress | Under way |
processing | Finished, being scored |
completed | Scored. The report is available |
cancelled | Ended without a result |
shortlisted and rejected also appear. Those are recruiter decisions made in
the dashboard, not something the assessment does to itself.
Sandbox assessments
A sandbox assessment behaves like a live one right up to scoring. It is never
recorded, so it never reaches completed and never produces a report. It also
never appears in your recruiters' pipeline and never spends a credit.
Build and verify in sandbox. Use live when you need the result.
Next
- Scheduling: set when the candidate is expected
- Reports: read the scored result
Coding tests and prompt engineering
Both pick their content from a catalog rather than taking free-form questions. List what your organisation can use, then pass the id:
curl https://hiring-api.experthire.cloud/v1/coding-tests \
-H "Authorization: Bearer $EH_KEY"
curl -X POST https://hiring-api.experthire.cloud/v1/assessments \
-H "Authorization: Bearer $EH_KEY" \
-d '{"type": "coding_test", "job_id": "...", "candidate_id": "...",
"coding_test_id": "..."}'
Still a placeholder: $EH_KEY. Add it under Your values above.
prompt_engineering works the same way with
GET /v1/prompt-engineering-assessments and
prompt_engineering_assessment_id.
Neither takes round_type, duration_minutes or questions. The catalog entry
decides the shape and the time limit. Neither can be scheduled either, because
there is no call to attend: the candidate takes it whenever they open the link.
Reusing the same catalog id on the same role reuses one configuration. A different id makes a new one.
If the module is not enabled for your organisation, the create returns
module_disabled and nothing is charged.
What the candidate does
Both run in the browser through a launch link, and the room drives them for you. If you are building your own surface instead, the calls are:
| Call | Coding | Prompt engineering |
|---|---|---|
POST /v1/assessments/{id}/start | Problems and progress | Tasks, results and progress |
POST /v1/assessments/{id}/run | Runs against sample cases, costs no attempt | n/a |
POST /v1/assessments/{id}/submissions | Submits a solution, costs an attempt | n/a |
POST /v1/assessments/{id}/attempts | n/a | Submits a prompt, costs an attempt |
POST /v1/assessments/{id}/finalize | Scores and closes | Scores and closes |
These take the candidate's session token, not your secret key.
finalize returns judging_in_progress (409) while work is still being scored.
Retry until it returns 200; the assessment is not finished before that.