Skip to main content

Reports

What comes back, and what to ignore.

GET /v1/assessments/{id}/report returns the scored result once the assessment has been scored. Before that it is a 404, deliberately, rather than a zero-scored report for something nobody has taken.

In sandbox this returns the real shape with fixed numbers and "sample": true. Sandbox never records, so nothing can be scored there; rather than a 404 you can build nothing against, you get the response you will have to parse. Never show a sample to a human, and never store its scores.

Do not gate on status == "completed". Shortlisting or rejecting a candidate overwrites the status in place, so a scored assessment often reads shortlisted or rejected. The report is available for all three.

The envelope

Both assessment types return the same envelope. The type-specific detail sits in feedback for an AI interview and in screening for a resume screen.

200AI interview, trimmed
{
  "object": "assessment_report",
  "assessment_id": "3b8e1d02-5c77-4c2a-8a41-9b2f7e6d4c10",
  "type": "ai_interview",
  "status": "completed",
  "livemode": true,
  "score": 68,
  "outcome": "good_fit",
  "summary": "Answered clearly with concrete examples, hesitant on trade-offs.",
  "question": "Walk me through a system you owned end to end.",
  "feedback": { "...": "see below" },
  "recording_url": "https://...",
  "transcript_url": "https://...",
  "created_at": 1767229200
}

outcome is good_fit, unlikely_fit or incomplete. It is empty until the scoring pipeline has run, which is the most reliable "is this scored yet" check.

Resume screens

A resume_screen carries screening instead of feedback. Every number is 0-100.

200Resume screen, trimmed
{
  "object": "assessment_report",
  "assessment_id": "9d41c7b8-2e35-4a19-b6f0-1c8a5d3e2f47",
  "type": "resume_screen",
  "status": "completed",
  "livemode": true,
  "score": 74,
  "outcome": "good_fit",
  "screening": {
    "ats_readiness": 81,
    "readability": 76,
    "experience_relevance": 72,
    "formatting": 68,
    "domain_skill_fit": 79,
    "must_have_coverage": 83,
    "good_to_have_coverage": 55,
    "weighted_skill_score": 75,
    "strengths": "Six years on payments infrastructure.",
    "weaknesses": "No evidence of team leadership."
  },
  "created_at": 1767229200
}

must_have_coverage and good_to_have_coverage are the percentage of each skill group the resume evidences. weighted_skill_score combines them at 70/30.

AI interview feedback

The report carries an overall score, a written summary, and a feedback object with the detail.

Three arrays inside feedback use different key names, which is the most common thing to get wrong:

ArrayKeysScale
speech_analysiscriteria, rating, comments1-100
behavioural_analysislabel, score, outcome0-100
field_knowledgecriteria, score, comments0-100

Note rating on one and score on the others. They are genuinely different fields, not one field described two ways.

200The three arrays, side by side
{
  "speech_analysis": [
    { "criteria": "Fluency", "rating": 72, "comments": "Steady pace, few fillers." }
  ],
  "behavioural_analysis": [
    { "label": "Posture", "score": 61, "outcome": "Composed" }
  ],
  "field_knowledge": [
    { "criteria": "Handling ambiguity", "score": 61, "comments": "One concrete example." }
  ]
}

Things that will mislead you

The audio-only sentinel. When there is no video, all three behavioural_analysis entries come back at score: 0 with outcome: "Not Analyzed (Audio Only)". That is "we did not measure this", not "this candidate scored zero". Check the outcome before rendering a number.

field_knowledge criteria are generated per transcript. The names differ between candidates, so they are readable but not comparable. Do not key a rubric off them.

Integrity signals travel with the scores. feedback also carries proctoring data. If you re-render the report to a candidate or a client, allowlist the keys you want rather than passing the object through.

A zero code_score does not mean nothing was written. Before scoring we check the submission is a genuine attempt at the question that was asked. Code that does not engage with it scores 0 across the board with the reason "No code available for evaluation", even though code.source is populated and comes back to you. Read code.summary to tell the two apart: it says outright when the submission was not a real attempt.

An interview round stores code, it does not run it. code_score is a model reading the submission against the question. Nothing is executed against test cases: that is what a coding_test assessment is for. code carries source, language and summary, and only the final state. Saves are last-write-wins, so there is no revision history and no per-submission timestamp.

Human interviews

A human_interview report is ready as soon as its summary is written, before it is scored. At that point it carries summary, interviewers (who was on your side of the call, with joined_at), transcript_url and, on live, recording_url. Once scoring finishes, score, outcome, feedback and section_scores are added. Sandbox is never scored, so its report stops at the summary.

Media

Recording and transcript URLs are signed and expire in two hours. Do not store them. Store the assessment id and re-fetch.

A field is absent when the artefact was never produced, which is normal for an assessment that ended early.

Transcript and recording

The report carries presigned recording_url and transcript_url links that expire in two hours, which is no use once you have stored the report. Two endpoints exist for reading them later:

  • GET /v1/assessments/{id}/transcript returns the turns themselves, in order, as JSON. Sandbox never records, so it returns a short fixed sample with sample: true. A human_interview is the exception: its lines come from each person's browser, so its sandbox transcript is real.
  • GET /v1/assessments/{id}/recording-url mints a fresh link each call and tells you whether the file is watermarked, which matters if you pass it on to a client.

What the room observed

GET /v1/assessments/{id}/events returns the proctoring signals, oldest first. ?category=integrity drops the benign activity markers and keeps the rest. An event name we do not recognise counts as an integrity signal rather than benign, so a new client cannot quietly hide something.

Do not confuse this with /v1/events, which is the webhook delivery log.

Recording your decision

Reading a report does not change anything. When you have decided, tell us:

curl -X POST https://hiring-api.experthire.cloud/v1/assessments/$ID/decision \
  -H "Authorization: Bearer $EH_KEY" \
  -d '{"outcome": "shortlisted"}'

Still a placeholder: $EH_KEY. Add it under Your values above.

outcome is shortlisted or rejected. It is refused with report_not_ready until the assessment has been scored.

This records the decision so it shows up for the recruiters working the role. It sends no email and moves nobody to a next round, because an assessment created over this API is a single round. Telling the candidate is yours to do.

Next