Interview as a Service
You already have an ATS, or your own hiring pipeline. You do not want Tarkflo's dashboard, its pipeline stages or its candidate emails. You just want one thing: a candidate interviewed by Tarkflo's AI, on Tarkflo's own interview page, with a result you can read back into your own system.
That is this flow. It is three calls, not two: Submit candidates is for a workspace that runs its pipeline in Tarkflo's dashboard. This is for one that does not.
create ──▶ interview_url, shown once ──▶ candidate opens it, interviews ──▶ poll status ──▶ completed ──▶ report
One-time setup: a job built for this
Create an interview needs a job whose workflow opens with an AI interview round —
nothing else happens first, no CV screen, no outreach step. Build this once in the Tarkflo dashboard: the
interview's questions, its rubric, how long it runs, and whether scheduling is required all live there, by design.
The API never creates or edits a workflow. See Read jobs, workflows and templates to confirm a
job is set up this way before using it here: its first round's interview_kind should be two_way.
A job set up any other way — a CV screen first, an outreach step first — gets 409 job_not_interview_ready. Use
Submit candidates for that job instead; this flow is specifically for a job with nothing
before the interview.
Create an interview
One candidate per call, not a batch: you are starting one interview, not filling a pipeline. Send an
Idempotency-Key, the same rule as Submit candidates.
- cURL
- Node.js
- Python
curl -X POST https://api.tarkflo.com/v1/interviews \
-H "Authorization: Bearer $TARKFLO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"job_id": "66f1c2a9b3d4e5f607182934",
"name": "Ananya Rao",
"email": "ananya.rao@example.com",
"external_id": "greenhouse-48213"
}'
import { randomUUID } from "node:crypto";
const res = await fetch("https://api.tarkflo.com/v1/interviews", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TARKFLO_API_KEY}`,
"Idempotency-Key": randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
job_id: "66f1c2a9b3d4e5f607182934",
name: "Ananya Rao",
email: "ananya.rao@example.com",
external_id: "greenhouse-48213",
}),
});
if (!res.ok) throw new Error(`Tarkflo ${res.status}: ${(await res.json()).message}`);
const interview = await res.json();
// Store interview.interview_url and interview.id now. The link is not returned again.
import os
import uuid
import requests
res = requests.post(
"https://api.tarkflo.com/v1/interviews",
headers={
"Authorization": f"Bearer {os.environ['TARKFLO_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"job_id": "66f1c2a9b3d4e5f607182934",
"name": "Ananya Rao",
"email": "ananya.rao@example.com",
"external_id": "greenhouse-48213",
},
timeout=30,
)
res.raise_for_status()
interview = res.json()
# Store interview["interview_url"] and interview["id"] now. The link is not returned again.
{
"id": "66f2d4b8e1a7c3095b6f8a21",
"job_id": "66f1c2a9b3d4e5f607182934",
"status": "ready",
"candidate": { "name": "Ananya Rao", "email": "ananya.rao@example.com", "external_id": "greenhouse-48213" },
"decision": null,
"scheduled_at": null,
"created_at": "2026-10-02T09:41:12.000Z",
"updated_at": "2026-10-02T09:41:12.000Z",
"interview_url": "https://hire.tarkflo.com/?token=x7k2m9qa4cjv1n0bRaNDoM",
"link_expires_at": "2026-10-16T09:41:12.000Z"
}
interview_url is shown once, in this response. Tarkflo stores only a hash of the token inside it, the same
way your own API key's secret works, so it cannot be read back by any other call. Store it immediately, next to
id. Hand it to the candidate however you already reach them: an email, a message, a link inside your own
product. Tarkflo never emails the candidate on this path. That is the point: your system owns that relationship.
If you lose the link before using it, call Get a fresh interview link. It mints a new token and invalidates the old one.
What the candidate sees
They open interview_url in a browser. If the job requires booking a time slot, they pick one first; otherwise
they start right away. Either way, this happens entirely on Tarkflo's own interview page — the API does not drive
it, and cannot skip it.
Poll status
curl "https://api.tarkflo.com/v1/interviews/66f2d4b8e1a7c3095b6f8a21" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
status | Meaning |
|---|---|
awaiting_schedule | The job requires booking a time slot, and the candidate has not booked one yet. |
scheduled | A slot is booked. scheduled_at says when the interview opens. |
ready | No scheduling is required. The link works as soon as the candidate opens it. |
in_progress | The candidate is interviewing right now. |
completed | Scored. Get an interview's report has something to return. |
abandoned | The candidate started and left before it finished. |
This never returns interview_url — there is nothing to read back once it was shown. Poll on whatever interval
suits you; it costs the same as any other read and shares your key's normal rate limit.
Get the result
Once status is completed:
curl "https://api.tarkflo.com/v1/interviews/66f2d4b8e1a7c3095b6f8a21/report" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
{
"interview_id": "66f2d4b8e1a7c3095b6f8a21",
"status": "completed",
"score": 82,
"verdict": "YES",
"decision": "HIRE",
"summary": "Strong on system design, communicated trade-offs clearly, no gaps on the role's core stack.",
"report_url": "https://hire.tarkflo.com/r/9f1c8a3b5d7e2f4061a8c9d0e1f2a3b4",
"report_url_expires_at": "2026-11-01T10:24:51.000Z"
}
score, verdict and decision are enough to drive a go/no-go in your own system. report_url is a scoped,
expiring link to the full report on Tarkflo, the same kind a recruiter shares from the dashboard — open it for the
competency breakdown and the AI's written reasoning. It never carries the transcript, the recording or the rubric
an answer was scored against: a leaked key or link cannot be used to see how the interview is graded, and this
read never gives you that either. Calling this endpoint again before the link expires returns the same
report_url, not a fresh one.
Called before completed, this returns 409 interview_not_ready. Check status first.
Instead of polling: webhooks
Set a webhook URL in Settings, Integrations and Tarkflo posts interview.completed the moment a round is
scored, report.ready right after. Both carry application_id (the same ID as interview_id here), and
report.ready includes report_url. Use polling for a simple integration, or combine both: a webhook tells you
when to stop polling.
Errors specific to this flow
| Status | Code | What it means |
|---|---|---|
| 409 | job_not_interview_ready | The job's workflow does not open with an AI interview round. Build one for this, or use Submit candidates for a different pipeline. |
| 409 | interview_not_ready | The report was requested before the interview completed. |
| 409 | interview_live | A relink was requested while the candidate is interviewing. Reissuing a link would end their session. |
| 404 | interview_not_found | The ID is wrong, or this key cannot see it. |
| 422 | no_candidates_created | The candidate could not be created. details.failed[0] says why, the same shape Submit candidates uses. |
See Errors for the full list every endpoint shares.