Read jobs, workflows and templates
Your hiring data is split into resources, and each has its own endpoint. A job names its workflow, a workflow names the template each interview round runs, and you follow the IDs.
job ──workflow.id──▶ workflow ──rounds[].template_id──▶ template
│
└── /stats how many have applied and where they are
| Resource | Endpoints | Gives you |
|---|---|---|
| Jobs | GET/jobs GET/jobs/{job_id} | Each job's title, status, how it is worked, where, how many applied, and the description |
| Job counts | GET/jobs/{job_id}/stats | Applicants by stage and by decision |
| Workflows | GET/workflows GET/workflows/{workflow_id} | The rounds, in order, and the jobs that run through them |
| Templates | GET/templates GET/templates/{template_id} | How an interview is set up, and the workflows that run it |
Every list pages with limit and a cursor. Jobs and counts need a key whose role includes jobs:view. Workflows and templates need templates:view, the permission the dashboard asks for before it shows them.
The API reads this data. It never edits it. Creating a workflow, changing a round or writing a question all happen in the Tarkflo dashboard.
Jobs
curl "https://api.tarkflo.com/v1/jobs?status=active&limit=25" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
{
"data": [
{
"id": "66f1c2a9b3d4e5f607182934",
"title": "Senior Backend Engineer",
"status": "active",
"department": "Engineering",
"employment_type": "full-time",
"location_type": "hybrid",
"location": "Bengaluru",
"applicant_count": 128,
"workflow": { "id": "66e8a01c5b7d2f3a90c4d1e2", "name": "Engineering, two rounds" },
"created_at": "2026-09-18T09:41:12.000Z"
}
],
"has_more": false,
"next_cursor": null
}
applicant_count is every application the job has ever received. It never goes down, even if a candidate is later removed.
One job adds the description, what it pays and the dates:
- cURL
- Node.js
- Python
curl "https://api.tarkflo.com/v1/jobs/66f1c2a9b3d4e5f607182934" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
const res = await fetch("https://api.tarkflo.com/v1/jobs/66f1c2a9b3d4e5f607182934", {
headers: { Authorization: `Bearer ${process.env.TARKFLO_API_KEY}` },
});
if (!res.ok) throw new Error(`Tarkflo ${res.status}: ${(await res.json()).message}`);
const job = await res.json();
console.log(`${job.title}: ${job.applicant_count} applied`);
console.log(`Workflow: ${job.workflow?.name ?? "none"}`);
import os
import requests
res = requests.get(
"https://api.tarkflo.com/v1/jobs/66f1c2a9b3d4e5f607182934",
headers={"Authorization": f"Bearer {os.environ['TARKFLO_API_KEY']}"},
timeout=30,
)
res.raise_for_status()
job = res.json()
print(f"{job['title']}: {job['applicant_count']} applied")
print("Workflow:", (job["workflow"] or {}).get("name", "none"))
{
"id": "66f1c2a9b3d4e5f607182934",
"title": "Senior Backend Engineer",
"status": "active",
"description": "Build the services behind our hiring platform.",
"department": "Engineering",
"employment_type": "full-time",
"positions": 2,
"compensation": { "kind": "salary", "currency": "INR", "min": 3000000, "max": 4500000, "per": null, "unpaid": false },
"workflow": { "id": "66e8a01c5b7d2f3a90c4d1e2", "name": "Engineering, two rounds" },
"applicant_count": 128
}
Fields a job does not state come back as null, so every field is always present. The job says which workflow it runs through and stops there. The rounds are the workflow's own data.
Job counts
How many have applied is one number. Where they are, and what has been decided, is its own resource, because it is the one read that scans a job's whole history.
curl "https://api.tarkflo.com/v1/jobs/66f1c2a9b3d4e5f607182934/stats" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
{
"job_id": "66f1c2a9b3d4e5f607182934",
"applicants": 128,
"in_pipeline": 121,
"by_stage": { "cv_screen": 34, "invited": 20, "in_progress": 6, "completed": 18, "reviewed": 43 },
"decisions": { "advanced": 22, "rejected": 31, "disqualified": 2, "hired": 3, "withdrawn": 4, "undecided": 59 },
"last_applied_at": "2026-10-01T08:12:45.000Z",
"views": 2310,
"as_of": "2026-10-01T09:00:00.000Z"
}
| Field | What it counts |
|---|---|
applicants | Every application the job has ever received. It never goes down. |
in_pipeline | Applications in the pipeline right now. This is lower than applicants when candidates have been removed. |
by_stage | Where those applications are today. Every stage is present, with 0 when empty. |
decisions | What has been decided about them. undecided is everyone still waiting. |
last_applied_at | When the most recent application came in. |
views | How many times the public job page was opened. |
as_of | When the counts were worked out. |
by_stage and decisions each add up to in_pipeline. A candidate is counted once in each. The counts are totals, and nothing in them identifies a candidate.
This endpoint is kept, on purpose. The counts are worked out at most once a minute per job and shared between callers, the response says Cache-Control: private, max-age=60, and each key may call it 30 times a minute. A dashboard that polls every few seconds gets the same answer, which is what you want. Use as_of to show how fresh it is.
Workflows
A workflow is the set of rounds candidates move through, and several jobs can run through the same one.
curl "https://api.tarkflo.com/v1/workflows/66e8a01c5b7d2f3a90c4d1e2" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
{
"id": "66e8a01c5b7d2f3a90c4d1e2",
"name": "Engineering, two rounds",
"description": "A phone screen, then a technical interview",
"status": "active",
"round_count": 2,
"job_count": 4,
"created_at": "2026-09-02T08:00:00.000Z",
"updated_at": "2026-09-20T11:30:00.000Z",
"rounds": [
{ "id": "step_1", "order": 1, "name": "Phone screen", "type": "interview", "interview_kind": "screening", "template_id": "66e8a0235b7d2f3a90c4d1f0" },
{ "id": "step_2", "order": 2, "name": "Technical deep dive", "type": "interview", "interview_kind": "two_way", "template_id": "66e8a0335b7d2f3a90c4d1f1" }
],
"jobs": [
{ "id": "66f1c2a9b3d4e5f607182934", "title": "Senior Backend Engineer", "status": "active" }
]
}
rounds are in the order candidates move through them. The type of a round is interview for a round candidates are interviewed in, and otherwise the kind of stage, such as a call, an assignment or an offer. New kinds can appear over time, so do not treat the list as closed. An interview round has an interview_kind: screening is a set of questions, and two_way is a live conversation.
jobs names up to 100 jobs, newest first, and job_count is always the true total. A job's own workflow carries the same id, so you can go from a job to its workflow and back.
How a round advances, and its pass marks, are set in the dashboard and are not in the response.
Templates
An interview round runs a template. Its template_id is the ID to read.
curl "https://api.tarkflo.com/v1/templates/66e8a0335b7d2f3a90c4d1f1" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
{
"id": "66e8a0335b7d2f3a90c4d1f1",
"template_number": 18,
"name": "Technical deep dive",
"description": "A 45 minute live technical interview",
"kind": "two_way",
"tags": ["backend"],
"is_system": false,
"job_id": null,
"workflow_count": 3,
"created_at": "2026-09-02T08:05:00.000Z",
"updated_at": "2026-09-20T11:30:00.000Z",
"screening": null,
"two_way": {
"mode": "JOB",
"level": "senior",
"persona": "technical",
"duration_minutes": 45,
"job_family": "engineering",
"focus_stack": ["node", "mongodb"],
"focus_areas": ["system design"],
"languages": ["en"],
"coding_round": true,
"scheduling_enabled": false
},
"workflows": [
{ "id": "66e8a01c5b7d2f3a90c4d1e2", "name": "Engineering, two rounds", "status": "active" }
]
}
A template is returned by how it is set up, never by what it asks. A screening template says how many questions it has and how it is run (screening). A two_way template says its length, level, persona, topics and languages (two_way). The questions themselves, the topics an interviewer probes and the rubric an answer is scored against stay in the dashboard, so a leaked key cannot be used to read an interview in advance.
workflow_count is how many workflow rounds run the template. workflows names up to 100 of them.
Errors
| Status | Code | What to do |
|---|---|---|
| 401 | invalid_api_key | The key is missing, mistyped, expired or revoked. |
| 403 | forbidden | The key's role lacks the permission: jobs:view for jobs and counts, templates:view for workflows and templates. The response names it in required. |
| 404 | job_not_found, workflow_not_found, template_not_found | The ID is wrong, or this key cannot see it. |
| 429 | rate_limited | Wait for the Retry-After header. Counts have their own, lower limit. |
See Errors for the full list.