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.
{
"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.
{
"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:
| Array | Keys | Scale |
|---|---|---|
speech_analysis | criteria, rating, comments | 1-100 |
behavioural_analysis | label, score, outcome | 0-100 |
field_knowledge | criteria, score, comments | 0-100 |
Note rating on one and score on the others. They are genuinely different
fields, not one field described two ways.
{
"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}/transcriptreturns the turns themselves, in order, as JSON. Sandbox never records, so it returns a short fixed sample withsample: true. Ahuman_interviewis the exception: its lines come from each person's browser, so its sandbox transcript is real.GET /v1/assessments/{id}/recording-urlmints a fresh link each call and tells you whether the file iswatermarked, 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.