Skip to main content

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
ResourceEndpointsGives you
JobsGET/jobs GET/jobs/{job_id}Each job's title, status, how it is worked, where, how many applied, and the description
Job countsGET/jobs/{job_id}/statsApplicants by stage and by decision
WorkflowsGET/workflows GET/workflows/{workflow_id}The rounds, in order, and the jobs that run through them
TemplatesGET/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 "https://api.tarkflo.com/v1/jobs/66f1c2a9b3d4e5f607182934" \
-H "Authorization: Bearer $TARKFLO_API_KEY"
{
"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"
}
FieldWhat it counts
applicantsEvery application the job has ever received. It never goes down.
in_pipelineApplications in the pipeline right now. This is lower than applicants when candidates have been removed.
by_stageWhere those applications are today. Every stage is present, with 0 when empty.
decisionsWhat has been decided about them. undecided is everyone still waiting.
last_applied_atWhen the most recent application came in.
viewsHow many times the public job page was opened.
as_ofWhen 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​

StatusCodeWhat to do
401invalid_api_keyThe key is missing, mistyped, expired or revoked.
403forbiddenThe key's role lacks the permission: jobs:view for jobs and counts, templates:view for workflows and templates. The response names it in required.
404job_not_found, workflow_not_found, template_not_foundThe ID is wrong, or this key cannot see it.
429rate_limitedWait for the Retry-After header. Counts have their own, lower limit.

See Errors for the full list.