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.
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.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.