Retrieve interview results and transcripts.
Retrieve complete interview results including transcripts.
GET /api/v1/interviews/:id
Headers:
Authorization: Bearer rz_live_...
Path Parameters:
id: Interview UUID200 OK (Completed interview):
{
"id": "660f9511-f30c-52e5-b827-557766551111",
"status": "completed",
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Senior Frontend Developer",
"department": "Engineering"
},
"candidate": {
"id": "770g0622-g41d-63f6-c938-668877662222",
"name": "Jane Smith",
"email": "jane@example.com"
},
"started_at": "2024-01-16T14:30:00Z",
"completed_at": "2024-01-16T14:52:00Z",
"responses": [
{
"question_id": "880h1733-h52e-74g7-d049-779988773333",
"question_text": "Describe your experience with React hooks and when you would use them.",
"question_type": "technical",
"transcript": "I've been using React hooks extensively for the past three years. Hooks like useState and useEffect are fundamental to my development workflow. I particularly appreciate how they enable functional components to manage state and side effects without the complexity of class components. For instance, I recently used useReducer for complex state logic in a form validation system, and useMemo to optimize expensive calculations in a data visualization dashboard. The custom hooks I've created have helped our team maintain consistent patterns across the codebase.",
"duration_seconds": 145,
"answered_at": "2024-01-16T14:33:25Z"
},
{
"question_id": "990i2844-i63f-85h8-e150-880099884444",
"question_text": "Tell me about a challenging bug you fixed recently.",
"question_type": "experience",
"transcript": "Last month, we had an intermittent race condition that only appeared in production...",
"duration_seconds": 167,
"answered_at": "2024-01-16T14:38:12Z"
}
],
"created_at": "2024-01-15T10:30:00Z"
}
Response fields:
id: Interview UUIDstatus: Interview status (completed, in_progress, invited, expired)job: Job informationcandidate: Candidate informationstarted_at: When candidate began interview (ISO 8601)completed_at: When interview finished (ISO 8601)responses[]: Array of question responsesresponses[].question_id: Question UUIDresponses[].question_text: The question that was askedresponses[].question_type: Question categoryresponses[].transcript: Full text transcription of responseresponses[].audio_url: URL to audio recording (temporary, expires in 24h)responses[].duration_seconds: Length of responseresponses[].answered_at: Timestamp of response200 OK (Non-completed interview):
{
"id": "660f9511-f30c-52e5-b827-557766551111",
"status": "invited",
"job": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Senior Frontend Developer"
},
"candidate": {
"id": "770g0622-g41d-63f6-c938-668877662222",
"name": "Jane Smith",
"email": "jane@example.com"
},
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2024-01-22T23:59:59Z"
}
curl https://rz-app-omega.vercel.app/api/v1/interviews/660f9511-f30c-52e5-b827-557766551111 \
-H "Authorization: Bearer rz_live_..."
async function getInterviewResults(interviewId) {
const response = await fetch(
`https://rz-app-omega.vercel.app/api/v1/interviews/${interviewId}`,
{
headers: {
'Authorization': `Bearer ${process.env.ROUNDZERO_API_KEY}`
}
}
);
if (!response.ok) {
throw new Error('Failed to fetch interview');
}
const interview = await response.json();
// Only completed interviews have responses
if (interview.status === 'completed') {
interview.responses.forEach(response => {
console.log(`Q: ${response.question_text}`);
console.log(`A: ${response.transcript}\n`);
});
}
return interview;
}
404 Not Found - Interview doesn't exist:
{
"error": {
"type": "resource_not_found",
"message": "Interview not found"
}
}
Retrieve the questions that were asked in an interview.
GET /api/v1/interviews/:id/questions
This endpoint returns the questions regardless of interview status.
Headers:
Authorization: Bearer rz_live_...
Path Parameters:
id: Interview UUID200 OK:
{
"interview_id": "660f9511-f30c-52e5-b827-557766551111",
"questions": [
{
"id": "880h1733-h52e-74g7-d049-779988773333",
"question_text": "Describe your experience with React hooks and when you would use them.",
"question_type": "technical",
"time_limit_seconds": 180,
"order": 0
},
{
"id": "990i2844-i63f-85h8-e150-880099884444",
"question_text": "Tell me about a challenging bug you fixed recently.",
"question_type": "experience",
"time_limit_seconds": 180,
"order": 1
},
{
"id": "custom-aa0j3955-j74g-96i9-f261-991100995555",
"question_text": "Why do you want to work at our company?",
"question_type": "behavioral",
"time_limit_seconds": 120,
"order": 10,
"is_custom": true
}
],
"total_questions": 11,
"estimated_duration_seconds": 1260
}
Response fields:
interview_id: Interview UUIDquestions[]: Array of questionsquestions[].is_custom: True if added via API (not from job template)total_questions: Count of questionsestimated_duration_seconds: Total time for all questionscurl https://rz-app-omega.vercel.app/api/v1/interviews/660f9511-f30c-52e5-b827-557766551111/questions \
-H "Authorization: Bearer rz_live_..."
invited
in_progress
completed
expired
Before fetching results, check status:
async function waitForCompletion(interviewId, maxWaitMs = 3600000) {
const startTime = Date.now();
while (Date.now() - startTime < maxWaitMs) {
const interview = await fetch(
`https://rz-app-omega.vercel.app/api/v1/interviews/${interviewId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
).then(r => r.json());
if (interview.status === 'completed') {
return interview;
}
if (interview.status === 'expired') {
throw new Error(`Interview ${interview.status}`);
}
// Wait before checking again
await new Promise(resolve => setTimeout(resolve, 60000)); // 1 minute
}
throw new Error('Timeout waiting for interview completion');
}
The API returns only what's necessary:
Sync interview results back to your ATS:
// Called when webhook receives "interview_ended" event
async function syncInterviewToATS(interviewId) {
// Get full interview results
const interview = await fetch(
`https://rz-app-omega.vercel.app/api/v1/interviews/${interviewId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
).then(r => r.json());
// Update candidate in ATS
await ats.updateCandidate({
id: interview.candidate.email,
screening_status: 'completed',
screening_completed_at: interview.completed_at,
screening_notes: formatTranscripts(interview.responses)
});
// Download and attach audio files
for (const response of interview.responses) {
const audioBlob = await fetch(response.audio_url).then(r => r.blob());
await ats.attachFile({
candidate_id: interview.candidate.email,
filename: `interview_q${response.question_id}.mp3`,
file: audioBlob
});
}
}
function formatTranscripts(responses) {
return responses.map((r, i) =>
`Q${i + 1}: ${r.question_text}\nA: ${r.transcript}\n`
).join('\n---\n\n');
}
Analyze transcripts programmatically:
async function analyzeInterview(interviewId) {
const interview = await fetch(
`https://rz-app-omega.vercel.app/api/v1/interviews/${interviewId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
).then(r => r.json());
const analysis = {
candidate: interview.candidate.name,
completedAt: interview.completed_at,
totalDuration: interview.responses.reduce((sum, r) => sum + r.duration_seconds, 0),
averageResponseLength: interview.responses.reduce((sum, r) => sum + r.transcript.length, 0) / interview.responses.length,
keywordMatches: analyzeKeywords(interview.responses)
};
return analysis;
}
function analyzeKeywords(responses) {
const keywords = ['react', 'typescript', 'testing', 'agile', 'ci/cd'];
const matches = {};
keywords.forEach(keyword => {
matches[keyword] = responses.filter(r =>
r.transcript.toLowerCase().includes(keyword)
).length;
});
return matches;
}
Export multiple interview results:
async function exportInterviews(interviewIds) {
const results = await Promise.all(
interviewIds.map(id =>
fetch(`https://rz-app-omega.vercel.app/api/v1/interviews/${id}`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
}).then(r => r.json())
)
);
// Convert to CSV
const csv = convertToCSV(results);
fs.writeFileSync('interview_export.csv', csv);
return results;
}
Don't poll - Use webhooks instead:
interview_ended eventCache interview results to reduce API calls:
Download audio files if needed long-term:
async function archiveAudio(interview) {
for (const response of interview.responses) {
const audio = await fetch(response.audio_url).then(r => r.arrayBuffer());
await storage.save(
`interviews/${interview.id}/q${response.question_id}.mp3`,
audio
);
}
}
Handle cases where interview isn't ready:
async function getResults(interviewId) {
const interview = await fetch(
`https://rz-app-omega.vercel.app/api/v1/interviews/${interviewId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
).then(r => r.json());
if (interview.status !== 'completed') {
console.log(`Interview not ready: ${interview.status}`);
return null;
}
return interview;
}