Build an AI Interviewer with the Waterr API
An AI interviewer is four API calls: a persona, a scenario with a scored script, a meeting the candidate joins, and a webhook that hands you the transcript and the scores. No SDK, plain REST. Here's the whole build, end to end, for a real role.
An AI interviewer is four API calls: a persona (who's asking), a scenario (the script and the goals it scores against), a meeting (the room a candidate joins), and a webhook (where the transcript, recording, and scores land). There's no SDK - Waterr's API is plain REST, base URL https://api.waterr.ai/v1, authenticated with a wai_ key in the Authorization header. This walks through the whole build for a real role - senior backend engineer, 30-minute screen, three scored goals - persona to webhook, with the exact request bodies.
The 30-minute phone screen doesn't scale
Every growing eng team runs into the same wall. Fifty resumes clear the first filter. Someone has to do a 30-minute screen with each one before a senior engineer's calendar gets touched for a real interview. That someone is usually the senior engineer, because nobody trusts a generic phone screen to catch whether a candidate actually understands tradeoffs or just knows the vocabulary. So the calendar fills with screens that are 80% the same six questions, and the senior engineer who should be shipping is instead asking "tell me about a challenging project" for the ninth time this month. The screen isn't the hard part of hiring. It's the part that shouldn't cost a senior engineer's week.
What you're building
A scenario that runs a technical screen for a Senior Backend Engineer role - system design, a past incident, follow-up pressure on vague answers - scored against three goals invisible to the candidate, with results pushed to your own endpoint the moment the call ends. Concretely, in order:
- Create the persona who conducts the interview
- Create the scenario with the meeting script
- Attach three scoring goals
- Create a meeting and get the candidate into it
- Receive the transcript, recording, and scores via webhook
- Extend it - vision, knowledge base, custom tools, memory
Step 0 - Get an API key
Get one at waterr.ai/settings?tab=api-keys. Send it on every request:
Authorization: Bearer wai_<your_key>Your workspace is inferred from the key - you never pass membership_id or org_id explicitly.
Step 1 - Create the persona
The persona is the character the candidate talks to.
curl -X POST https://api.waterr.ai/v1/personas \
-H "Authorization: Bearer wai_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Alex Rivera",
"job_title": "Staff Engineer, Distributed Systems",
"demeanor": "analytical",
"background": "8 years building infrastructure at a cloud platform company. Runs technical screens for the backend team. Direct, pushes back on hand-wavy answers, digs into tradeoffs before moving on."
}'demeanor is an enum - analytical fits a technical screen better than friendly or enthusiastic. The response gives you an id. That's your PERSONA_ID for the next step.
Step 2 - Create the scenario with a meeting script
The prompt field is doing the real work here - it's the single biggest factor in whether the conversation feels like a real interview or a chatbot reading questions off a list. A good script has five parts: identity, style, flow with timing, behavioral rules, and guardrails.
curl -X POST https://api.waterr.ai/v1/scenarios \
-H "Authorization: Bearer wai_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Senior Backend Engineer - Screen",
"description": "30-minute technical screen for the backend team: system design and a past production incident.",
"type": "interview",
"persona_id": "PERSONA_ID",
"welcome_message": "Hi, I'\''m Alex. Let'\''s talk about how you build systems.",
"prompt": "You are Alex Rivera, Staff Engineer on the distributed systems team at a mid-stage cloud company.\n\n## Style\n- Direct and technical. Skip pleasantries after the opening.\n- Ask one question, listen fully, then respond.\n- Push back on hand-wavy answers: \"How specifically would you handle that?\"\n- When they give a good answer, acknowledge briefly and go deeper.\n\n## Flow\n1. Brief intro and role overview (2 min)\n2. \"Walk me through the most complex backend system you'\''ve built\" (8 min)\n3. System design: \"Design a rate limiter that handles 100K requests/sec\" (15 min)\n4. \"Tell me about a production incident you debugged\" (5 min)\n\n## Rules\n- If they get stuck on the design, give ONE hint, then move on\n- Never confirm the \"right\" answer\n- If they ask about salary, say HR covers that in the next round\n\n## Guardrails\n- Stay in character throughout\n- Never break the fourth wall (\"I'\''m an AI\")\n- End with: \"Thanks - do you have questions for me?\""
}'type must be one of interview, roleplay, upskill, brainstorm - this one's interview. The response returns the scenario's id, which every following step needs.
Two things worth knowing before you ship a script: the AI's replies are spoken aloud via text-to-speech, so write for the ear, not the page - short turns, no bullet points read out loud. And avoid over-scripting every response; give the persona rules and phases, not a transcript to recite, or the conversation feels robotic.
Step 3 - Add scoring goals
Goals are the evaluation criteria - invisible to the candidate, visible only in the analysis afterward. Each one links to the scenario via scenario_id.
curl -X POST https://api.waterr.ai/v1/goals \
-H "Authorization: Bearer wai_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Technical Depth",
"description": "Depth and accuracy of technical explanations on the system design question.",
"instructions": "Score 8-10 if they reason about tradeoffs (latency vs. consistency, memory vs. throughput) unprompted. Score 4-6 if they need the hint to get there. Score 1-3 if the design ignores scale entirely.",
"scenario_id": "SCENARIO_ID"
}'Repeat the same call twice more - same fields, different content:
| Goal | Scoring instructions (the gist) |
|---|---|
| Problem Solving | High score if they break the rate limiter down into sub-problems (counting, storage, distribution) before proposing a solution. Low score if they jump straight to an answer with no decomposition. |
| Communication | High score if answers are structured - states the approach, walks through it, then summarizes. Low score if answers ramble or require repeated clarifying questions. |
Write scoring instructions that are specific and behavioral. "Good communication" gives the model nothing to grab onto. "States the approach, then walks through it, then summarizes" gives it a rubric.
Step 4 - Create the meeting and get the candidate in
One call creates the session:
curl -X POST https://api.waterr.ai/v1/meetings \
-H "Authorization: Bearer wai_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"person_name": "Priya Nair",
"scenario_id": "SCENARIO_ID",
"note_context": "8 years experience, most recent role on a payments platform team. Weight the system design question toward idempotency, not generic rate limiting."
}'note_context injects session-specific detail into the AI's prompt without touching the shared scenario script - useful when you already know something about the candidate (their resume, their last role) and want the interview to reference it. The response:
{
"success": true,
"data": {
"id": "meeting-uuid",
"daily_meeting_url": "https://waterr-xx.daily.co/abc123",
"room_token": "eyJhbGciOiJIUzI1NiIs...",
"meeting_url": "https://waterr.ai/meeting/meeting-uuid",
"status": "created"
}
}Send meeting_url to the candidate and the session is live - no download, no account, just a browser tab. Three other ways to get people in, depending on the shape of your pipeline: a share link (waterr.ai/{username}/{scenario-slug}) if candidates should be able to start a screen anytime; a booking page if you'd rather let them pick their own slot; or an embed token if the screen needs to run inside your own authenticated ATS page, with the candidate's identity locked in server-side.
Step 5 - Receive the results
Register an endpoint once, and every meeting on your account fires to it:
curl -X POST https://api.waterr.ai/v1/webhooks/endpoints \
-H "Authorization: Bearer wai_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/waterr",
"description": "ATS sync",
"subscribed_events": ["session.analysis_complete", "transcript.ready", "recording.ready"]
}'The response includes signing_secret (whsec_…) - shown once, store it now. Verify every incoming request against the Waterr-Signature header before trusting the body:
const crypto = require('crypto');
const TOLERANCE = 5 * 60;
function verify(rawBody, header, secret) {
if (!header) return false;
const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > TOLERANCE) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
app.post('/webhooks/waterr', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, req.headers['waterr-signature'], process.env.WATERR_WEBHOOK_SECRET)) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(req.body);
if (event.event === 'session.analysis_complete') {
const { meeting_id, analysis } = event.data;
// analysis.average_score, analysis.goal_results, analysis.summary
}
res.sendStatus(200); // always 2xx, or Waterr retries
});Prefer pulling instead of receiving pushes? Three GET calls cover it: GET /analyses/meeting/{meetingId} for scores and feedback, GET /transcripts/meeting/{meetingId} for the full transcript, GET /recordings/meeting/{meetingId} for the video. Analysis typically lands 30-60 seconds after the candidate hangs up.
Step 6 - Extend it
Once the base screen works, these are the additions that turn it from "a bot that asks questions" into something worth a hiring team's trust:
- Vision - turn on camera and screen share on the scenario and Alex can watch a live coding walkthrough, not just listen to it, commenting on what's actually on the whiteboard. See Vision.
- Knowledge base - attach the actual job description as a file. The interviewer cites the real role instead of a generic backend posting. See Knowledge Base.
- Multi-language - no config needed; the AI detects the candidate's spoken language and evaluates in it.
- Participant memory - turn it on and a signed-in candidate's second-round screen picks up their first-round context instead of re-asking what they already covered. See Participant Memory.
- Custom tools - register a
lookup_candidatefunction once, attach it to the scenario, and Alex pulls the candidate's real CV from your ATS mid-call before asking about their actual last project. See Custom Functions. - White-label - Enterprise plans run the whole flow under your own domain, no Waterr logo in the candidate's browser.
And for the build itself: install the Claude Code skill (npx skills add waterrai/skills) and your coding agent already knows this API - it scopes what you're building, then writes the integration citing every endpoint it touches. Or connect the hosted MCP server at https://api.waterr.ai/v1/mcp and just ask your agent how yesterday's screens scored.
If you're deciding whether to build this yourself against a general-purpose voice model or use an AI interviewer API that already handles the video room, turn-taking, and scoring, that tradeoff is worth reading before you write more code - see Build vs. Buy: AI Meetings API. And if "scenario," "persona," and "goals" are new vocabulary, What Is an AI Meeting API? covers the primitives this whole post assumes.
Frequently asked questions
How long does a session run? Default maximum is 35 minutes per scenario. It's configurable per scenario if a screen needs more or less room - a 30-minute technical screen and a 15-minute culture chat don't need the same ceiling.
Can I test without a real candidate?
Yes. Every new account is auto-seeded with two ready-to-use scenarios (GET /scenarios returns them with default_kind: "plus_one" and default_kind: "requirement_gathering"). You can also just create a meeting with your own name as person_name and run through your own script before sending it to anyone else.
How do I get scoring results programmatically?
Either register a webhook endpoint and receive session.analysis_complete the moment a screen ends, or poll GET /analyses/meeting/{meetingId} - it typically returns 30-60 seconds after the call ends, with average_score, per-goal goal_results, strengths, and growth_areas.
Do I need an SDK to build this?
No. There isn't one - the API is plain REST. Every example above is a curl call or a fetch/requests call you can port to whatever language your backend already runs.
Can candidates join without installing anything?
Yes. meeting_url opens in any modern browser over WebRTC - no app, no account, no plugin. Mic and camera permissions are requested in-browser, same as joining a Zoom link.
The screen was never the part of hiring that needed a senior engineer's judgment. It was the part that ate their calendar before their judgment ever got used. Build it once, and every candidate after the first one runs through the same rubric, at any hour, without anyone's week getting shorter for it.
