Skip to main content

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.

200Response
{
  "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
}
StatusMeaning
pendingCreated, not yet scheduled
scheduledReady for the candidate
in_progressUnder way
processingFinished, being scored
completedScored. The report is available
cancelledEnded 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

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:

CallCodingPrompt engineering
POST /v1/assessments/{id}/startProblems and progressTasks, results and progress
POST /v1/assessments/{id}/runRuns against sample cases, costs no attemptn/a
POST /v1/assessments/{id}/submissionsSubmits a solution, costs an attemptn/a
POST /v1/assessments/{id}/attemptsn/aSubmits a prompt, costs an attempt
POST /v1/assessments/{id}/finalizeScores and closesScores 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.