Developers · Agent API, SDK & MCP

Put Kumo inside your product

Embed Kumo's complete model and run loop, point your own agents at Kumo, or connect Kumo to the systems you already run. Every request is a named user; every model call, approval, tool call, receipt, and outcome is traceable.

A token acts as you

Tokens are minted per user, per workspace. The API applies the same visibility model as the app: self-service users see themselves, managers their team, HR the workspace. Scopes can only narrow that further.

One declaration layer

The REST endpoints, the OpenAPI spec, this reference, and the MCP server's tool list are all generated from the same operation registry the runtime executes. Docs cannot go stale by construction.

Everything is audited

Writes run through the platform's own engines: validation, approvals, notifications included. Every MCP call, inbound or outbound, lands in the audit log with who, what, and when.

Kumo technology products

The runtime, API, protocol, and kit.

Four named products, one permission and audit model: Kumo AI Platform, Kumo API, Kumo MCP, and Kumo HR Agent Kit SDK.

01Agent runtime

Kumo AI Platform

Kumo’s model, planner, durable run loop, approvals, co-signature, artifacts, memory, credits, and tamper-evident audit in one governed platform.

  • Named user on every request
  • Human gates on commitment
  • Complete correlated trace
Technical reference
02Application surface

Kumo API

Embed the complete Kumo Agent or call individual HR operations through a versioned REST and Agent API with delegated identity.

  • Agent API + REST
  • Resumable SSE + signed webhooks
  • Generated OpenAPI contract
Technical reference
03Universal integration

Kumo MCP

A matched server, client, and outbound Connector. Your systems call Kumo; Kumo calls the named tools you choose to publish.

  • Protocol, not a catalogue
  • No inbound port with Connector
  • Receipts, attribution, idempotency
Technical reference
04Embedding toolkit

Kumo HR Agent Kit SDK

A typed TypeScript client, headless controller, and accessible React panel for putting Kumo inside a customer application.

  • Short-lived delegated credentials
  • Stream reconnect + decisions
  • Webhook verification
Technical reference
How it runs

One agent platform. Three paths.

Use Kumo's complete agent in your product, let another agent call Kumo, or let Kumo act across your systems. All three preserve named-user permissions, approvals, credits, and the same tamper-evident audit trail.

Embed

Your product uses Kumo's agent

The Agent API and SDK run Kumo's own model, planner, durable loop, approvals, artifacts, and audit trail inside your customer experience.

Embed Kumo →

Inbound

Your agents call Kumo

Claude Code, Cursor, VS Code, or anything you have built yourself posts JSON-RPC to https://kumohr.com/api/mcp with a personal access token. The tool list is your API, filtered to that token's scopes.

Set up inbound →

Outbound

Kumo calls your systems

An admin connects any MCP server under Settings → Connections: a published URL, or an outbound connector that dials out from inside your network. Every external action waits for a person to approve it.

Set up outbound →
Authentication

Personal access tokens

Create a token in Kumo under Settings → API access. Pick a name and the scopes it should carry. The secret is shown exactly once. Send it on every request; revoke it any time from the same screen. After you mint one, the same connect buttons appear with the token already filled in.

curl https://kumohr.com/api/v1/me \ -H "Authorization: Bearer kumo_…"
leave:readRead leave requests and balances
leave:writeCreate leave requests
reports:readRun read-only reports
ats:readRead recruiting: jobs, candidates, applications, pipeline stages
ats:writeAdd candidates, create applications, move applications between stages
ats:assessments:writePost assessment results (AI interviews, tests) back to applications
webhooks:manageSubscribe to event webhooks and send test events
ats:transcripts:readRead interview transcripts (audit-logged)
ats:jobs:writeCreate, draft (with Kumo AI) and publish jobs; set hiring stages
ats:assessments:manageCreate and draft (with Kumo AI) screening forms and AI interview plans
ats:assessments:sendSend Kumo screening assessments and AI interviews to candidates
agent:runStart and continue Kumo agent work
agent:readRead agent conversations, runs, and events
agent:approveApprove or decline agent actions
agent:artifactsUpload inputs and retrieve agent deliverables
audit:readRead and export the agent audit trail

Need a token? Sign in and open Settings → API access.

Embed Kumo

The complete Agent API

This is Kumo's model and durable run loop, not a customer-supplied chatbot. Your backend exchanges one admin-created integration credential for a short-lived credential delegated to an existing Kumo user. Runs inherit that user's live tenant role; scopes can only narrow it.

import { KumoAgentClient } from "@kumohr/agent-sdk"; const integration = new KumoAgentClient({ token: process.env.KUMO_INTEGRATION_CREDENTIAL }); const delegated = await integration.exchangeCredential({ userId: kumoUserId, externalSessionId: yourSessionId, scopes: ["agent:run", "agent:read", "agent:approve", "agent:artifacts"] }); const kumo = new KumoAgentClient({ token: delegated.accessToken }); const thread = await kumo.createThread({ title: "People operations" }); const result = await kumo.sendMessage(thread.data.thread.id, { message: "Check overdue training and prepare manager nudges." }); for await (const event of kumo.streamRun(result.data.runId)) { console.log(event); }

Credentials in the account

Personal tokens and admin integration credentials are created under Settings → API access. Secrets are shown once, stored as hashes, independently scoped, expirable, and revocable. Integration credentials stay on your backend; browsers receive only short-lived delegated credentials.

Agent endpoints

OperationScopeEndpoint
exchange_agent_tokenagent:runPOST /api/v1/agent/token
list_agent_threadsagent:readGET /api/v1/agent/threads
create_agent_threadagent:runPOST /api/v1/agent/threads
get_agent_threadagent:readGET /api/v1/agent/threads/{id}
send_agent_messageagent:runPOST /api/v1/agent/threads/{id}/messages
get_agent_runagent:readGET /api/v1/agent/runs/{id}
stream_agent_runagent:readGET /api/v1/agent/runs/{id}/events
decide_agent_runagent:approvePOST /api/v1/agent/runs/{id}/decisions
answer_agent_runagent:runPOST /api/v1/agent/runs/{id}/answers
cancel_agent_runagent:approvePOST /api/v1/agent/runs/{id}/cancel
decide_agent_actionagent:approvePOST /api/v1/agent/runs/{id}/actions/{actionId}/decisions
upload_agent_attachmentagent:artifactsPOST /api/v1/agent/attachments
list_agent_artifactsagent:artifactsGET /api/v1/agent/artifacts
get_agent_artifactagent:artifactsGET /api/v1/agent/runs/{id}/artifacts/{artifactId}
download_agent_artifactagent:artifactsGET /api/v1/agent/runs/{id}/artifacts/{artifactId}/download
list_agent_auditaudit:readGET /api/v1/agent/audit
export_agent_auditaudit:readGET /api/v1/agent/audit/export
verify_agent_auditaudit:readPOST /api/v1/agent/audit/verify
list_agent_webhooksagent:readGET /api/v1/agent/webhooks
create_agent_webhookagent:runPOST /api/v1/agent/webhooks
disable_agent_webhookagent:runDELETE /api/v1/agent/webhooks/{id}

Mutations use Idempotency-Key. Run events are resumable SSE with event IDs. Webhooks are signed and retried. Responses carry request and audit correlation IDs so one external request can be followed through the model, approval, tool, connected system, receipt, artifact, credits, and final outcome.

Inbound MCP

Connect your agent

The Kumo MCP server

Point any MCP-capable agent at https://kumohr.com/api/mcp (streamable HTTP, server kumo-hr) with your token in the Authorization header. Protocol versions 2025-06-18 and 2025-03-26. Its tool list is your API: the same operations below, filtered to your token's scopes.

Add to Cursor →

Opens Cursor and asks you to confirm the server.

Any other client reads the same thing from a standard mcp.json:

{ "mcpServers": { "kumo-hr": { "type": "http", "url": "https://kumohr.com/api/mcp", "headers": { "Authorization": "Bearer kumo_YOUR_TOKEN" } } } }
Role-bound

Your agent can only do what your role allows. The same RBAC that gates the app gates every tool call; the token cannot escalate.

Scope-filtered

tools/list only advertises operations the token's scopes permit, so an agent holding a read-only token never even sees the write tools.

Fully audited

Every tool call is recorded in the audit log: which token, which tool, which arguments, and whether it succeeded. You can see everything it did.

Inbound tools

The tools your agent gets

One tool per operation, named after it. A token only sees the rows its scopes cover. This list is generated from the same registry the server executes.

ToolScopeSame as
get_meany tokenGET /api/v1/me
list_leave_requestsleave:readGET /api/v1/leave/requests
create_leave_requestleave:writePOST /api/v1/leave/requests
untaken_leave_reportreports:readGET /api/v1/reports/untaken-leave
list_ats_jobsats:readGET /api/v1/ats/jobs
list_ats_candidatesats:readGET /api/v1/ats/candidates
create_ats_candidateats:writePOST /api/v1/ats/candidates
import_ats_candidatesats:writePOST /api/v1/ats/candidates/import
list_ats_applicationsats:readGET /api/v1/ats/applications
create_ats_applicationats:writePOST /api/v1/ats/applications
move_ats_applicationats:writePOST /api/v1/ats/applications/move
list_ats_pipeline_stagesats:readGET /api/v1/ats/pipeline/stages
search_candidates_aiats:readPOST /api/v1/ats/candidates/search
list_webhookswebhooks:manageGET /api/v1/webhooks
create_webhookwebhooks:managePOST /api/v1/webhooks
delete_webhookwebhooks:manageDELETE /api/v1/webhooks
test_webhookwebhooks:managePOST /api/v1/webhooks/test
record_ats_assessmentats:assessments:writePOST /api/v1/ats/assessments
list_ats_assessmentsats:readGET /api/v1/ats/assessments
list_ats_assessment_routesats:readGET /api/v1/ats/assessment-routes
set_ats_assessment_routeats:writePOST /api/v1/ats/assessment-routes
get_ats_candidate_resumeats:readGET /api/v1/ats/candidates/resume
get_ats_assessmentats:readGET /api/v1/ats/assessments/detail
decide_ats_assessmentats:writePOST /api/v1/ats/assessments/decision
get_job_criteriaats:readGET /api/v1/ats/jobs/criteria
set_job_criteriaats:writePOST /api/v1/ats/jobs/criteria
match_talent_poolats:writePOST /api/v1/ats/jobs/match-pool
get_match_resultsats:readGET /api/v1/ats/jobs/match-pool
place_matched_candidatesats:writePOST /api/v1/ats/jobs/match-pool/place
draft_job_with_aiats:jobs:writePOST /api/v1/ats/jobs/draft
create_jobats:jobs:writePOST /api/v1/ats/jobs
publish_jobats:jobs:writePOST /api/v1/ats/jobs/publish
set_job_stagesats:jobs:writePOST /api/v1/ats/jobs/stages
list_assessment_templatesats:readGET /api/v1/ats/assessment-templates
save_assessment_templateats:assessments:managePOST /api/v1/ats/assessment-templates
draft_assessment_with_aiats:assessments:managePOST /api/v1/ats/assessment-templates/draft
send_assessmentats:assessments:sendPOST /api/v1/ats/assessments/send

What inbound covers today

37 operations, and tools only; no MCP resources or prompts yet. Authentication is a personal access token in a header, which covers Cursor, Claude Code, and VS Code. Hosted connectors that require OAuth, such as ChatGPT, cannot connect yet. We would rather say so than ship a button that fails.

Outbound MCP

Connect your systems

Kumo is also an MCP client. An admin allow-lists a server under Settings → Connections. Its tools join the agent's catalogue as mcp__<name>__<tool>. This is how Kumo reaches a calendar, a chat tool, an ERP, an LMS, or anything else that speaks MCP, not a special case for one vendor.

Always confirmed

External tools never run unattended. The person sees which system, which operation, and the arguments, then approves. There is no setting that turns this off.

Attributed

Each call carries who asked, which workspace, and which approved action as headers and JSON-RPC metadata, so it shows against the right name in that system's own audit trail.

Repeat-safe

Approving a retried step sends the same idempotency key, so an adapter that honours it cannot apply the same change twice.

Two ways to reach the server

If the system can publish an https MCP endpoint, register that URL. If it lives on a network that will never accept inbound traffic, add an outbound connector instead: same tools, same approval, no inbound port.

Outbound connector

When nothing can call in

You run a small process beside the system you already have. It dials out to Kumo, publishes the tools on the local adapter, and takes approved calls. No inbound firewall rule, no public URL, no certificate for you to manage.

# Inside your network: one outbound HTTPS connection KUMO_URL=https://kumohr.com \ KUMO_CONNECTOR_TOKEN=kumo_cn_… \ MCP_URL=http://127.0.0.1:8799/mcp \ npm run mcp:connector

Add the connector in Settings → Connections. The pairing token is shown once. Point MCP_URL at any local MCP adapter: the system you already run, or a thin wrapper beside it.

API reference

REST endpoints

Base URL https://kumohr.com. Responses use a { data } / { error: { code, message } } envelope with X-RateLimit-* headers. The machine-readable version of this reference lives at /api/v1/openapi.json.

GET/api/v1/meany token

Introspect the token

Returns the authenticated user, workspace (tenant), role, employee id, and the scopes this token carries. Use it to verify a token before wiring anything else.

curl https://kumohr.com/api/v1/me \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "token": { "name": "Zapier integration", "scopes": [ "leave:read", "reports:read" ] }, "user": { "id": "uuid", "name": "Amara Okafor", "email": "amara@acme.com", "role": "HR_MANAGER" }, "tenant": { "id": "uuid", "name": "Acme Ltd" }, "employee_id": 214 } }
GET/api/v1/leave/requestsleave:read

List leave requests

Lists leave requests you are allowed to see: your own for self-service roles, your team’s for line managers, your department’s for directors, the whole workspace for HR and admins. Filter by status and date range.

ParameterTypeDescription
statusstringFilter by status. One of: PENDING, APPROVED, REJECTED, CANCELLED, IN_PROGRESS, COMPLETED.
fromstringOnly requests ending on or after this date (YYYY-MM-DD).
tostringOnly requests starting on or before this date (YYYY-MM-DD).
limitnumberMax rows to return (1–200, default 50).
curl https://kumohr.com/api/v1/leave/requests?status=…&from=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "requests": [ { "id": "uuid", "employee_id": 214, "employee_name": "Amara Okafor", "policy": "Annual Leave", "start_date": "2026-09-07", "end_date": "2026-09-11", "total_days": 5, "half_day": false, "status": "APPROVED", "reason": "Family trip", "created_at": "2026-08-30T09:15:00.000Z" } ] } }
POST/api/v1/leave/requestsleave:write

Create a leave request

Books time off for the token’s user through the platform’s own leave engine. Policy validation, balance movement, approval routing, and notifications all run exactly as from the app. Pass start_date plus either end_date or days; the policy defaults to annual leave.

ParameterTypeDescription
start_daterequiredstringFirst day off (YYYY-MM-DD).
end_datestringLast day off, inclusive (YYYY-MM-DD). Use this OR days.
daysnumberNumber of consecutive calendar days off. Use this OR end_date.
policystringLeave policy name, e.g. "Annual Leave". Defaults to the annual policy.
policy_idstringExact policy id (overrides policy).
reasonstringOptional short reason.
half_daybooleanTrue for a single half-day request.
curl -X POST https://kumohr.com/api/v1/leave/requests \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"start_date":"2026-09-07","days":2}'
// 2xx response { "data": { "id": "uuid", "status": "PENDING", "policy": "Annual Leave", "start_date": "2026-09-07", "end_date": "2026-09-08", "total_days": 2, "warnings": [] } }
GET/api/v1/reports/untaken-leavereports:read

Untaken leave report

Per-employee untaken leave for a year: allocated, used, and remaining days with department and manager, sorted by most untaken first. Runs through the same permission-gated reporting tool Kumo’s agent uses. Your role decides whose rows you see.

ParameterTypeDescription
yearnumberBalance year (defaults to the current year).
min_untaken_daysnumberOnly employees with at least this many untaken days.
policystringLeave policy name filter (defaults to annual policies).
curl https://kumohr.com/api/v1/reports/untaken-leave?year=…&min_untaken_days=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "year": 2026, "employees": [ { "employee": "Amara Okafor", "department": "Engineering", "manager": "Lena Fischer", "allocated_days": 25, "used_days": 6, "untaken_days": 19 } ], "totals": { "employees": 42, "untakenDays": 512 } } }
GET/api/v1/ats/jobsats:read

List jobs

Jobs (requisitions) in the workspace that this token can see. Managers with assigned-only recruiting access see only the jobs they work on.

ParameterTypeDescription
statusstringFilter by status, e.g. ACTIVE, DRAFT, CLOSED. Default: all.
searchstringFree-text search over title and requisition code.
pagenumberPage number, from 1.
limitnumberRows per page (1–100, default 25).
curl https://kumohr.com/api/v1/ats/jobs?status=…&search=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "jobs": [ { "id": "uuid", "title": "Senior Backend Engineer", "requisition_code": "ENG-042", "department": "Engineering", "status": "ACTIVE", "location": "Jakarta", "work_type": "Full-time", "experience_level": "Senior", "description": "We are looking for…", "requirements": "5+ years of Python…", "skills_required": [ "Python", "PostgreSQL" ], "skills_preferred": [ "Kubernetes" ], "applications_count": 27, "created_at": "2026-09-01T09:00:00.000Z", "updated_at": "2026-09-20T12:30:00.000Z" } ], "pagination": { "page": 1, "limit": 25, "total": 1, "total_pages": 1 } } }
GET/api/v1/ats/candidatesats:read

List or find candidates

Candidates in the talent pool, with their applications. Pass `email` for an exact lookup (the way to check whether a candidate already exists).

ParameterTypeDescription
emailstringExact email match.
searchstringFree-text search over name, email and title.
stagestringOnly candidates currently in this stage.
statusstringCandidate status, e.g. NEW, ACTIVE, HIRED, REJECTED.
pagenumberPage number, from 1.
limitnumberRows per page (1–100, default 25).
curl https://kumohr.com/api/v1/ats/candidates?email=…&search=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "candidates": [ { "id": "uuid", "first_name": "Jane", "last_name": "Doe", "email": "jane.doe@example.com", "phone": "+62 812 0000 0000", "location": "Jakarta", "current_title": "Backend Engineer", "current_company": "Acme", "linkedin_url": null, "source": "API", "status": "NEW", "stage": "New Application", "skills": [ "Python", "Django" ], "tags": [ "referral" ], "match_score": 78, "created_at": "2026-09-25T08:00:00.000Z", "applications": [ { "id": "uuid", "job_id": "uuid", "job_title": "Senior Backend Engineer", "stage": "Matched Profile", "status": "NEW" } ] } ], "pagination": { "page": 1, "limit": 25, "total": 1, "total_pages": 1 } } }
POST/api/v1/ats/candidatesats:write

Add a candidate

Adds a candidate, or updates them if they already exist. Kumo matches first on `source_system` + `external_id` (your system’s id, so a changed email still finds the same person), then on email; a match is updated with the fields you send and returned with `created: false`. Attach a CV with `resume_url` (an https link Kumo downloads) or `resume_base64`: PDF or Word, up to 10 MB. A new or changed CV is scored against the job and your company; an unchanged CV is not re-scored. Pass `job_id` to apply them to a job (the application starts in the job’s first stage).

ParameterTypeDescription
first_namerequiredstringFirst name.
last_namerequiredstringLast name.
emailrequiredstringEmail address — the dedupe key.
phonestringPhone number, with country code.
locationstringCity and/or country.
current_titlestringCurrent job title.
current_companystringCurrent employer.
linkedin_urlstringLinkedIn profile URL.
skillsstringSkills, comma-separated (or a JSON array of strings).
tagsstringTags, comma-separated (or a JSON array of strings).
notesstringFree-text recruiter notes.
sourcestringWhere the candidate came from, shown to recruiters. Default: the source_system name, or API.
source_systemstringYour system, e.g. "greenhouse" or "client-ats". With external_id, the match key.
external_idstringThe candidate’s id in your system. Sending the same one again updates the same candidate.
resume_urlstringhttps link to the CV (PDF or Word, up to 10 MB). Kumo downloads it once.
resume_base64stringThe CV file itself, base64-encoded, instead of resume_url. Requests are limited to about 4.5 MB, so use resume_url for CVs over about 3 MB.
resume_filenamestringFile name for the CV, e.g. "jane-doe.pdf".
job_idstringAlso apply the candidate to this job.
curl -X POST https://kumohr.com/api/v1/ats/candidates \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"first_name":"2026-09-07","last_name":"2026-09-07","email":"2026-09-07"}'
// 2xx response { "data": { "candidate": { "id": "uuid", "first_name": "Jane", "last_name": "Doe", "email": "jane.doe@example.com", "phone": "+62 812 0000 0000", "location": "Jakarta", "current_title": "Backend Engineer", "current_company": "Acme", "linkedin_url": null, "source": "API", "status": "NEW", "stage": "New Application", "skills": [ "Python", "Django" ], "tags": [ "referral" ], "match_score": 78, "created_at": "2026-09-25T08:00:00.000Z", "applications": [ { "id": "uuid", "job_id": "uuid", "job_title": "Senior Backend Engineer", "stage": "Matched Profile", "status": "NEW" } ] }, "created": true, "application": { "id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "stage": "Matched Profile", "status": "NEW", "source": "API", "applied_at": "2026-09-25T08:00:00.000Z", "updated_at": "2026-09-25T08:00:00.000Z", "candidate": { "id": "uuid", "name": "Jane Doe", "email": "jane.doe@example.com" }, "job": { "id": "uuid", "title": "Senior Backend Engineer" } }, "resume": "attached" } }
POST/api/v1/ats/candidates/importats:write

Add or update many candidates

Up to 100 candidates per call, each handled exactly like “Add a candidate” (matched on source_system + external_id, then email; CVs by link or base64). `job_id`, `source_system` and `source` at the top level apply to every candidate unless a candidate sets its own. One bad candidate never fails the others: each result says ok or why not.

ParameterTypeDescription
candidatesrequiredarrayThe candidates: the same fields as “Add a candidate”.
job_idstringApply every candidate to this job.
source_systemstringYour system, for every candidate.
sourcestringWhere they came from, for every candidate.
curl -X POST https://kumohr.com/api/v1/ats/candidates/import \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"candidates":"2026-09-07"}'
// 2xx response { "data": { "results": [ { "index": 0, "ok": true, "candidate_id": "c71e0000-0000-4000-8000-000000000001", "created": true, "application_id": "a1b20000-0000-4000-8000-000000000001", "resume": "attached" }, { "index": 1, "ok": false, "error": { "code": "invalid_resume", "message": "The CV must be a PDF or Word document." } } ], "summary": { "received": 2, "created": 1, "updated": 0, "failed": 1 } } }
GET/api/v1/ats/applicationsats:read

List applications

Applications — a candidate applied to a job — with their current pipeline stage. Filter by job to read a job’s board.

ParameterTypeDescription
job_idstringOnly applications to this job.
candidate_idstringOnly this candidate’s applications.
stagestringOnly applications in this stage.
statusstringApplication status, e.g. NEW, INTERVIEW, OFFER, HIRED, REJECTED.
pagenumberPage number, from 1.
limitnumberRows per page (1–100, default 25).
curl https://kumohr.com/api/v1/ats/applications?job_id=…&candidate_id=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "applications": [ { "id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "stage": "Matched Profile", "status": "NEW", "source": "API", "applied_at": "2026-09-25T08:00:00.000Z", "updated_at": "2026-09-25T08:00:00.000Z", "candidate": { "id": "uuid", "name": "Jane Doe", "email": "jane.doe@example.com" }, "job": { "id": "uuid", "title": "Senior Backend Engineer" } } ], "pagination": { "page": 1, "limit": 25, "total": 1, "total_pages": 1 } } }
POST/api/v1/ats/applicationsats:write

Apply a candidate to a job

Creates an application for an existing candidate. Idempotent per candidate and job. Pass `stage` to place it straight into a pipeline stage (for example after your own screening); otherwise it starts in the job’s first stage.

ParameterTypeDescription
candidate_idrequiredstringThe candidate.
job_idrequiredstringThe job.
stagestringPipeline stage to place the application in. See list_ats_pipeline_stages.
sourcestringWhere the application came from. Default: API.
curl -X POST https://kumohr.com/api/v1/ats/applications \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"candidate_id":"2026-09-07","job_id":"2026-09-07"}'
// 2xx response { "data": { "application": { "id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "stage": "Matched Profile", "status": "NEW", "source": "API", "applied_at": "2026-09-25T08:00:00.000Z", "updated_at": "2026-09-25T08:00:00.000Z", "candidate": { "id": "uuid", "name": "Jane Doe", "email": "jane.doe@example.com" }, "job": { "id": "uuid", "title": "Senior Backend Engineer" } }, "created": true } }
POST/api/v1/ats/applications/moveats:write

Move an application to a stage

Moves one application to another stage of its job’s pipeline — the same as dragging the card on the board, with the same activity log. The stage must be one of the job’s stages (see list_ats_pipeline_stages); other applications of the candidate are not touched.

ParameterTypeDescription
application_idrequiredstringThe application to move.
stagerequiredstringTarget stage name (case-insensitive).
reasonstringOptional note for the activity log.
curl -X POST https://kumohr.com/api/v1/ats/applications/move \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"application_id":"2026-09-07","stage":"2026-09-07"}'
// 2xx response { "data": { "application": { "id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "stage": "Passed Interview", "status": "NEW", "source": "API", "applied_at": "2026-09-25T08:00:00.000Z", "updated_at": "2026-09-25T08:00:00.000Z", "candidate": { "id": "uuid", "name": "Jane Doe", "email": "jane.doe@example.com" }, "job": { "id": "uuid", "title": "Senior Backend Engineer" } }, "movement": { "from": "AI Interview", "to": "Passed Interview" } } }
GET/api/v1/ats/pipeline/stagesats:read

List pipeline stages

The stages an application can move through. With `job_id`, the job’s own pipeline when it has one; otherwise the workspace’s default pipeline.

ParameterTypeDescription
job_idstringThe job whose pipeline to read.
curl https://kumohr.com/api/v1/ats/pipeline/stages?job_id=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "job_id": "uuid", "source": "job", "stages": [ { "name": "Matched Profile", "order": 1, "color": "#3b82f6" }, { "name": "AI Interview", "order": 2, "color": "#8b5cf6" }, { "name": "Passed Interview", "order": 3, "color": "#10b981" } ] } }
POST/api/v1/ats/candidates/searchats:read

Find candidates (AI search)

Search the whole talent pool the way a recruiter would ask: "senior data engineers in Jakarta with Spark", or pass `job_id` to rank the pool against that job’s criteria. Returns people best first with matched skills and reasons. Pass `required_skills`/`preferred_skills`/`locations`/`min_years` yourself to skip Kumo’s request parsing (no AI credits); otherwise parsing is one metered call billed to the workspace, and `explain: true` adds a one-line reason per result (one more call). Requests that filter on age, gender, religion, nationality, marital status, health or appearance are refused with 422.

ParameterTypeDescription
querystringWho you are looking for, in plain words.
job_idstringRank the pool for this job (its criteria and stored fit scores).
required_skillsarraySkills every result must have (up to 3).
preferred_skillsarraySkills that rank people higher (up to 8).
locationsarrayCities, regions or countries.
min_yearsnumberMinimum years of experience (people with no recorded experience are kept, ranked lower).
filtersobjectHard filters: {skills, locations, minYears, maxYears, sources, tags, appliedToJobId, hasCv, activeWithinDays}.
modestring`ai` (default) parses the query with a model; `basic` never calls a model. One of: ai, basic.
explainbooleanAdd a one-line reason and a profile quote per result (metered).
limitnumberResults to return, 1-50 (default 20).
curl -X POST https://kumohr.com/api/v1/ats/candidates/search \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{}'
// 2xx response { "data": { "request": { "summary": "Senior data engineers in Jakarta", "requiredSkills": [ "Python" ], "preferredSkills": [ "Apache Spark" ], "locations": [ "Jakarta" ], "minYears": 5 }, "job": null, "searched": 412, "by_meaning": true, "parsed_by": "ai", "credits_charged": 1, "candidates": [ { "id": "uuid", "first_name": "Intan", "last_name": "Permata", "title": "Data Engineer", "location": "Jakarta", "years_experience": 6, "match": 91, "matched_skills": [ "Python", "Apache Spark" ], "missing_skills": [], "reasons": [ "Python, Apache Spark", "6 yrs", "Jakarta" ], "why": "Builds Spark pipelines on AWS; six years in data engineering.", "evidence_quote": "Built Airflow pipelines", "job_fit": null, "meets_must_haves": null } ] } }
GET/api/v1/webhookswebhooks:manage

List webhooks

Active webhook endpoints in the workspace, their filters, and the 50 most recent deliveries with status and response code.

curl https://kumohr.com/api/v1/webhooks \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "endpoints": [ { "id": "uuid", "label": "Genia AI interviews", "url": "https://api.genia.ai/kumo/events", "events": [ "ats.application.stage_entered" ], "filters": { "stages": [ "AI Interview" ] }, "enabled": true, "created_at": "2026-09-25T08:00:00.000Z" } ], "recent_deliveries": [], "supported_events": [ "agent.run.completed", "agent.run.failed", "agent.run.cancelled", "agent.action.required", "agent.artifact.created", "agent.connector.health", "ats.application.stage_entered", "ats.application.created", "ats.assessment.recorded", "ats.assessment.invited", "ats.assessment.started", "ats.assessment.declined", "ats.job.created", "ats.job.published" ] } }
POST/api/v1/webhookswebhooks:manage

Subscribe to events

Registers an https endpoint for signed event deliveries. Narrow it with `filters` — e.g. `{"stages":["AI Interview"]}` delivers only when an application enters that stage. The `signing_secret` is returned once; use it to verify the `Kumo-Webhook-Signature` header.

ParameterTypeDescription
urlrequiredstringYour https endpoint.
eventsrequiredstringEvent types (JSON array or comma-separated). Recruiting: ats.application.stage_entered, ats.application.created, ats.assessment.recorded, ats.assessment.invited, ats.assessment.started, ats.assessment.declined, ats.job.created, ats.job.published.
filtersobjectOptional: {"stages": [stage names], "job_ids": [job ids]}.
labelstringA name for this endpoint.
curl -X POST https://kumohr.com/api/v1/webhooks \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"url":"2026-09-07","events":"2026-09-07"}'
// 2xx response { "data": { "endpoint": { "id": "uuid", "label": "Genia AI interviews", "url": "https://api.genia.ai/kumo/events", "events": [ "ats.application.stage_entered" ], "filters": { "stages": [ "AI Interview" ] }, "enabled": true, "created_at": "2026-09-25T08:00:00.000Z" }, "signing_secret": "kumo_wh_…" } }
DELETE/api/v1/webhookswebhooks:manage

Remove a webhook

Stops deliveries to an endpoint. Pending deliveries to it are dropped.

ParameterTypeDescription
idrequiredstringThe webhook id.
curl -X POST https://kumohr.com/api/v1/webhooks \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{}'
// 2xx response { "data": { "id": "uuid", "enabled": false } }
POST/api/v1/webhooks/testwebhooks:manage

Send a test event

Sends a signed sample `ats.application.stage_entered` event (marked `"test": true`, with sample candidate and job data) to the endpoint right away and reports what your server answered.

ParameterTypeDescription
idrequiredstringThe webhook id.
curl -X POST https://kumohr.com/api/v1/webhooks/test \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"id":"2026-09-07"}'
// 2xx response { "data": { "event_id": "evt_…", "delivered": true, "status": "delivered", "response_status": 200, "error": null } }
POST/api/v1/ats/assessmentsats:assessments:write

Post an assessment result

Records a result from your assessment (AI interview, test, check) against an application. Idempotent by `provider` + `external_id`: post each status change for the same session and it updates one record. When `status` is `completed` and the workspace has a route for the application’s current stage, Kumo moves the application to the pass or fail stage (from `passed`, or `score` against the route’s minimum) — unless a recruiter has already moved it.

ParameterTypeDescription
application_idrequiredstringThe application (from the webhook payload).
providerrequiredstringYour product key, e.g. "genia". Stable across calls.
external_idrequiredstringYour id for this session or test.
statusrequiredstringWhere the assessment is. One of: pending, invited, in_progress, completed, expired, declined, cancelled, error.
scorenumberOverall score, 0–100.
passedbooleanYour recommendation. Omit to let the route’s minimum score decide.
summarystringConclusion shown to recruiters.
strengthsstringStrengths.
weaknessesstringWeaknesses / concerns.
report_urlstringhttps link to the full report or recording.
rawobjectYour full result as JSON (up to 256 KB), kept for recruiters.
recommendationstringYour recommendation for the recruiter. One of: advance, hold, reject.
confidencenumberHow sure you are, 0–1.
modestringHow the interview ran. One of: voice, text.
disclosureobjectWhat you told the candidate before starting: {text, version?, language?: en|id, mode?: voice|text, shown_at?}. Shown to recruiters as “What they were told”.
preferred_humanbooleanWith status "declined": the candidate asked to speak with a person. Never scored; the hiring team is told.
sentimentobjectYour sentiment read, if you produce one: a label such as "positive", or {label, score?: -100 to 100, notes?}. Shown to recruiters as context; never used to move a candidate.
kindstringai_interview (default), skills_test, async_video, reference_check or custom.
dimension_scoresarrayPer-dimension scores shown as bars: [{key, label, score 0-100, weight?, evidence: [{quote, t_ms?}]}] (up to 20).
transcriptarrayThe interview transcript: [{role: interviewer|candidate, text, t_ms?}] (up to 400 turns). Evidence timestamps jump to it.
curl -X POST https://kumohr.com/api/v1/ats/assessments \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"application_id":"2026-09-07","provider":"2026-09-07","external_id":"2026-09-07","status":"2026-09-07"}'
// 2xx response { "data": { "assessment": { "id": "uuid", "application_id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "provider": "genia", "external_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "completed", "score": 78, "passed": true, "summary": "Strong problem-solving and clear communication…", "strengths": "System design, communication", "weaknesses": "Limited large-scale database experience", "report_url": "https://app.genia.ai/sessions/f47ac10b", "routed_to_stage": "Passed Interview", "routed_at": "2026-09-25T10:42:00.000Z", "completed_at": "2026-09-25T10:41:57.000Z", "created_at": "2026-09-25T09:00:00.000Z", "updated_at": "2026-09-25T10:42:00.000Z" }, "routing": { "action": "moved", "outcome": "passed", "to_stage": "Passed Interview" } } }
GET/api/v1/ats/assessmentsats:read

List assessment results

Assessment results recorded for an application or candidate, newest first.

ParameterTypeDescription
application_idstringResults for this application.
candidate_idstringResults for this candidate.
curl https://kumohr.com/api/v1/ats/assessments?application_id=…&candidate_id=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "assessments": [ { "id": "uuid", "application_id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "provider": "genia", "external_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "completed", "score": 78, "passed": true, "summary": "Strong problem-solving and clear communication…", "strengths": "System design, communication", "weaknesses": "Limited large-scale database experience", "report_url": "https://app.genia.ai/sessions/f47ac10b", "routed_to_stage": "Passed Interview", "routed_at": "2026-09-25T10:42:00.000Z", "completed_at": "2026-09-25T10:41:57.000Z", "created_at": "2026-09-25T09:00:00.000Z", "updated_at": "2026-09-25T10:42:00.000Z" } ] } }
GET/api/v1/ats/assessment-routesats:read

List assessment routes

Where completed assessments send applications: for each trigger stage, the pass and fail stages.

curl https://kumohr.com/api/v1/ats/assessment-routes \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "routes": [ { "id": "uuid", "job_id": null, "provider": "genia", "trigger_stage": "AI Interview", "pass_stage": "Passed Interview", "fail_stage": "Failed Interview", "min_score": 70, "enabled": true } ] } }
POST/api/v1/ats/assessment-routesats:write

Set an assessment route

Sets where a completed assessment sends an application that is in `trigger_stage`. Workspace-wide by default; pass `job_id` for one job and `provider` for one partner. Replaces any existing route for the same job, provider and trigger stage. Workspace admins only.

ParameterTypeDescription
trigger_stagerequiredstringThe stage the assessment runs in, e.g. "AI Interview".
pass_stagerequiredstringWhere a pass goes.
fail_stagerequiredstringWhere a fail goes.
min_scorenumberPass mark (0–100) used when a result has a score but no `passed`.
job_idstringOnly for this job.
providerstringOnly for results from this provider.
fail_policystringOn a fail: move_and_review (default: move to the fail stage and flag for a recruiter), move, or review (keep the card where it is until a recruiter decides). One of: move_and_review, move, review.
curl -X POST https://kumohr.com/api/v1/ats/assessment-routes \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"trigger_stage":"2026-09-07","pass_stage":"2026-09-07","fail_stage":"2026-09-07"}'
// 2xx response { "data": { "route": { "id": "uuid", "job_id": null, "provider": "genia", "trigger_stage": "AI Interview", "pass_stage": "Passed Interview", "fail_stage": "Failed Interview", "min_score": 70, "enabled": true } } }
GET/api/v1/ats/candidates/resumeats:read

Get a candidate’s CV

A download link for the candidate’s CV that expires after 15 minutes. Webhook payloads carry this path in `links.resume` rather than a file or a long-lived link. Needs full recruiting access.

ParameterTypeDescription
candidate_idrequiredstringThe candidate.
curl https://kumohr.com/api/v1/ats/candidates/resume?candidate_id=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "candidate_id": "uuid", "url": "https://…/resumes/…/jane-doe.pdf?token=…", "filename": "jane-doe.pdf", "expires_at": "2026-09-25T10:15:00.000Z" } }
GET/api/v1/ats/assessments/detailats:read

Get one assessment

One assessment result with dimension scores, recommendation and review state. Add `include=transcript` for the transcript (needs the ats:transcripts:read scope; every read is audit-logged).

ParameterTypeDescription
idrequiredstringThe assessment id.
includestringComma-separated extras: transcript.
curl https://kumohr.com/api/v1/ats/assessments/detail?id=…&include=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "assessment": { "id": "uuid", "application_id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "provider": "genia", "external_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "completed", "score": 78, "passed": true, "summary": "Strong problem-solving and clear communication…", "strengths": "System design, communication", "weaknesses": "Limited large-scale database experience", "report_url": "https://app.genia.ai/sessions/f47ac10b", "routed_to_stage": "Passed Interview", "routed_at": "2026-09-25T10:42:00.000Z", "completed_at": "2026-09-25T10:41:57.000Z", "created_at": "2026-09-25T09:00:00.000Z", "updated_at": "2026-09-25T10:42:00.000Z" } } }
POST/api/v1/ats/assessments/decisionats:write

Record a recruiter decision

For partners with their own review screen: advance (to the route’s pass stage), reject (to its fail stage) or hold. Going against the result’s recommendation needs a reason. Marks the result reviewed.

ParameterTypeDescription
idrequiredstringThe assessment id.
decisionrequiredstringThe decision. One of: advance, hold, reject.
reasonstringWhy (required when overriding the recommendation).
curl -X POST https://kumohr.com/api/v1/ats/assessments/decision \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"id":"2026-09-07","decision":"2026-09-07"}'
// 2xx response { "data": { "assessment": { "id": "uuid", "application_id": "uuid", "candidate_id": "uuid", "job_id": "uuid", "provider": "genia", "external_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status": "completed", "score": 78, "passed": true, "summary": "Strong problem-solving and clear communication…", "strengths": "System design, communication", "weaknesses": "Limited large-scale database experience", "report_url": "https://app.genia.ai/sessions/f47ac10b", "routed_to_stage": "Passed Interview", "routed_at": "2026-09-25T10:42:00.000Z", "completed_at": "2026-09-25T10:41:57.000Z", "created_at": "2026-09-25T09:00:00.000Z", "updated_at": "2026-09-25T10:42:00.000Z", "review_state": "reviewed", "decision": "advance" }, "moved_to": "Passed Interview" } }
GET/api/v1/ats/jobs/criteriaats:read

Get a job’s hiring criteria

The criteria a job is scored against, with importance, weight, knockout flag and any fairness warnings. `defined: false` means they were derived from the job’s listed skills and the workspace has not saved its own yet.

ParameterTypeDescription
job_idrequiredstringThe job.
curl https://kumohr.com/api/v1/ats/jobs/criteria?job_id=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "job_id": "uuid", "defined": true, "match_threshold": 70, "criteria": [ { "key": "python", "label": "Python in production", "category": "skill", "importance": "must_have", "weight": null, "knockout": true, "min_years": 3, "evidence_hint": "Services or systems built in Python", "warnings": [] }, { "key": "postgresql", "label": "PostgreSQL", "category": "skill", "importance": "important", "weight": null, "knockout": false, "min_years": null, "evidence_hint": null, "warnings": [] }, { "key": "kafka", "label": "Event streaming (Kafka or similar)", "category": "skill", "importance": "nice_to_have", "weight": null, "knockout": false, "min_years": null, "evidence_hint": null, "warnings": [] } ] } }
POST/api/v1/ats/jobs/criteriaats:write

Set a job’s hiring criteria

Replaces a job’s criteria. Up to 30; `importance` is must_have, important or nice_to_have; `knockout` applies to must-haves only and flags candidates for review — nobody is rejected automatically. Criteria that could discriminate (age, gender, “native speaker”…) are refused unless `lint_justification` gives a job-related reason. Changing weights or importance re-scores for free; new or reworded criteria are assessed on the next scoring run.

ParameterTypeDescription
job_idrequiredstringThe job.
criteriarequiredarrayList of {label, category?, importance?, weight?, knockout?, min_years?, evidence_hint?, lint_justification?}.
match_thresholdnumberJob-fit score (0–100) for talent-pool placement. Null uses the workspace default.
curl -X POST https://kumohr.com/api/v1/ats/jobs/criteria \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"job_id":"2026-09-07","criteria":"2026-09-07"}'
// 2xx response { "data": { "job_id": "uuid", "criteria": [ { "key": "python", "label": "Python in production", "category": "skill", "importance": "must_have", "weight": null, "knockout": true, "min_years": 3, "evidence_hint": "Services or systems built in Python", "warnings": [] }, { "key": "postgresql", "label": "PostgreSQL", "category": "skill", "importance": "important", "weight": null, "knockout": false, "min_years": null, "evidence_hint": null, "warnings": [] }, { "key": "kafka", "label": "Event streaming (Kafka or similar)", "category": "skill", "importance": "nice_to_have", "weight": null, "knockout": false, "min_years": null, "evidence_hint": null, "warnings": [] } ] } }
POST/api/v1/ats/jobs/match-poolats:write

Match the talent pool against a job

Scores every candidate in the workspace’s talent pool against the job’s criteria, in the background. Candidates already scored for the current criteria cost nothing. Poll get_match_results for progress and the ranking; nothing is placed until you call place_matched_candidates.

ParameterTypeDescription
job_idrequiredstringThe job.
curl -X POST https://kumohr.com/api/v1/ats/jobs/match-pool \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"job_id":"2026-09-07"}'
// 2xx response { "data": { "run_id": "uuid", "pool_size": 142, "already_scored": 38, "estimated_credits": 208 } }
GET/api/v1/ats/jobs/match-poolats:read

Get talent-pool match results

Progress of the latest match run for a job, and candidates ranked by job-fit score with must-have status and whether they are already in the pipeline.

ParameterTypeDescription
job_idrequiredstringThe job.
curl https://kumohr.com/api/v1/ats/jobs/match-pool?job_id=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "run": { "id": "uuid", "status": "completed", "total": 142, "scored": 140, "failed": 2, "credits_used": 180, "threshold": 70 }, "results": [ { "candidate_id": "uuid", "name": "Ayu Pratama", "score": 88, "meets_all_must_haves": true, "failed_knockouts": [], "criteria_evidenced": 7, "criteria_total": 8, "in_pipeline": false, "stage": null } ] } }
POST/api/v1/ats/jobs/match-pool/placeats:write

Add matched candidates to the pipeline

Creates an application in the job’s first stage (e.g. “Matched Profile”) for each candidate. Candidates already in the pipeline are skipped. Emits ats.application.created.

ParameterTypeDescription
job_idrequiredstringThe job.
candidate_idsrequiredarrayCandidates to place (up to 500).
curl -X POST https://kumohr.com/api/v1/ats/jobs/match-pool/place \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"job_id":"2026-09-07","candidate_ids":"2026-09-07"}'
// 2xx response { "data": { "placed": 12, "already_applied": 1 } }
POST/api/v1/ats/jobs/draftats:jobs:write

Draft a job with Kumo AI

Describe the role in plain words ("senior data engineer, hybrid Jakarta, Python/Spark/Airflow") and Kumo drafts the whole job: the ad, hiring criteria, application questions, the hiring stages and an assessment per stage (a screening form or an AI interview). When required facts are missing it returns `needs_input: true` with up to three `follow_ups`; send the answers back in `answers` (keyed by follow-up id; answer `skip` for optional ones). Nothing is saved — pass the draft to `create_job`. Metered: about 1 credit to read the brief and 2 per drafting call (typically 8-12 credits for a full job), billed to the workspace. AI-written screening forms are created as drafts that a person must approve before they can be sent.

ParameterTypeDescription
briefrequiredstringWhat you are hiring for, in your own words (any language).
answersobjectAnswers to earlier follow-ups: {title, seniority, where, salary}.
curl -X POST https://kumohr.com/api/v1/ats/jobs/draft \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"brief":"2026-09-07"}'
// 2xx response { "data": { "needs_input": false, "credits_charged": 10, "draft": { "role": { "title": "Senior Data Engineer", "department": "Engineering", "experienceLevel": "Senior", "workArrangement": "Hybrid", "location": "Jakarta", "salaryMin": 25000000, "salaryMax": 35000000, "salaryCurrency": "IDR" }, "content": { "description": "<p>…</p>", "responsibilities": "<ul><li>…</li></ul>", "requirements": "<ul>…</ul>", "benefits": "" }, "criteria": [ { "label": "Has run Airflow DAGs in production, including retries and backfills", "category": "skill", "importance": "must_have" } ], "stages": [ { "name": "Screening", "assessment": { "type": "form", "name": "Data engineering screen", "config": { "passMark": 70, "questions": [ { "id": "rtw", "type": "knockout", "prompt": "Do you have the right to work in Indonesia?", "knockoutAnswer": "yes", "required": true, "weight": 0 }, { "id": "backfill", "type": "single_choice", "prompt": "A daily DAG failed for 3 days and upstream data is now fixed. What do you do?", "options": [ { "id": "o1", "label": "Clear the failed runs and backfill those dates", "correct": true }, { "id": "o2", "label": "Trigger one run for today" } ], "required": true, "weight": 1 } ] } } } ], "notes": [ "No benefits were given — add yours before publishing" ] } } }
POST/api/v1/ats/jobsats:jobs:write

Create a job

Create a job (as a draft unless `publish: true`) from a `draft` returned by `draft_job_with_ai`, or from your own fields. Stages, criteria, application questions and each stage’s assessment are saved in the same call; stage assessments become stage automations (sent automatically when a candidate enters the stage, with pass/fail routing and fails held for review by default). Approval policies still apply: a job that needs approval is created and waits. No AI credits are used.

ParameterTypeDescription
draftobjectA draft from draft_job_with_ai (edited as you like).
titlestringWithout a draft: the job title.
departmentstringWithout a draft: the department.
locationstringCity or office (required unless remote).
work_arrangementstringOnsite, Hybrid or Remote. One of: Onsite, Hybrid, Remote.
descriptionstringThe job ad (HTML or text).
stagesarrayHiring stages: [{name, objective?, assessment?: {type: none|form|ai_interview|template|partner, …}, pass_stage?, fail_policy?}].
requisition_codestringYour own requisition number (e.g. from your HR system). Omit and Kumo numbers it (REQ-2026-0042). Unique per workspace.
publishbooleanPublish immediately (subject to approval).
curl -X POST https://kumohr.com/api/v1/ats/jobs \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{}'
// 2xx response { "data": { "job": { "id": "uuid", "title": "Senior Data Engineer", "status": "DRAFT", "requisition_code": "REQ-2026-0042" }, "extras": { "criteria": { "saved": 8 }, "stages": [ { "stage": "Screening", "templateId": "uuid", "automationId": "uuid" } ] } } }
POST/api/v1/ats/jobs/publishats:jobs:write

Publish a job

Take a draft job live on the careers page. Refused with 409 while the job waits for approval. A job with no stages gets the workspace’s default stages.

ParameterTypeDescription
job_idrequiredstringThe job.
curl -X POST https://kumohr.com/api/v1/ats/jobs/publish \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"job_id":"2026-09-07"}'
// 2xx response { "data": { "job": { "id": "uuid", "status": "ACTIVE" }, "already_live": false } }
POST/api/v1/ats/jobs/stagesats:jobs:write

Set a job’s hiring stages and assessments

Replace a job’s stage plan safely: stages are matched by id or name and updated in place, so candidates already in a stage keep their place; a removed stage that still has history is deactivated, not deleted; renames carry over to automations and routing. Each stage can carry an assessment (`form`, `ai_interview`, an existing `template`, or a `partner`), which becomes its stage automation.

ParameterTypeDescription
job_idrequiredstringThe job.
stagesrequiredarray[{id?, name, objective?, assessment?, pass_stage?, fail_stage?, fail_policy?}] in order.
curl -X POST https://kumohr.com/api/v1/ats/jobs/stages \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"job_id":"2026-09-07","stages":"2026-09-07"}'
// 2xx response { "data": { "stages": [ { "id": "uuid", "name": "Screening" } ], "renamed": [], "deactivated": [], "assessments": [ { "stage": "Screening", "templateId": "uuid", "automationId": "uuid" } ] } }
GET/api/v1/ats/assessment-templatesats:read

List assessment templates

Screening forms and AI interview plans — a job’s own plus the workspace library — with the credit cost per candidate.

ParameterTypeDescription
job_idstringOnly this job’s templates (and the library).
kindstringform or ai_interview. One of: form, ai_interview.
curl https://kumohr.com/api/v1/ats/assessment-templates?job_id=…&kind=… \ -H "Authorization: Bearer kumo_…"
// 2xx response { "data": { "templates": [ { "id": "uuid", "job_id": "uuid", "kind": "form", "purpose": "assessment", "name": "Data engineering screen", "status": "active", "version": 1, "source": "ai", "config": { "passMark": 70, "questions": [ { "id": "rtw", "type": "knockout", "prompt": "Do you have the right to work in Indonesia?", "knockoutAnswer": "yes", "required": true, "weight": 0 }, { "id": "backfill", "type": "single_choice", "prompt": "A daily DAG failed for 3 days and upstream data is now fixed. What do you do?", "options": [ { "id": "o1", "label": "Clear the failed runs and backfill those dates", "correct": true }, { "id": "o2", "label": "Trigger one run for today" } ], "required": true, "weight": 1 } ] }, "estimate": { "perCandidate": 2, "maxPerCandidate": 2, "lines": [ "1 written answer scored by Kumo · 2 credits per candidate" ] } } ] } }
POST/api/v1/ats/assessment-templatesats:assessments:manage

Create or update an assessment template

A screening form (`kind: form`: knockout, single/multi choice with right answers or points, number ranges, short/long written answers with a rubric) or an AI interview plan (`kind: ai_interview`). Pass `id` to update; candidates who already have a link keep the version they were sent. Questions about protected characteristics are rejected. Set `status: active` to approve a draft (AI-written forms start as drafts).

ParameterTypeDescription
idstringUpdate this template.
job_idstringAttach to a job (omit for the workspace library).
kindstringform or ai_interview. One of: form, ai_interview.
namerequiredstringName shown to candidates.
configrequiredobjectThe questions or interview plan (see the example).
statusstringdraft or active. One of: draft, active.
curl -X POST https://kumohr.com/api/v1/ats/assessment-templates \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"name":"2026-09-07","config":"2026-09-07"}'
// 2xx response { "data": { "template": { "id": "uuid", "job_id": "uuid", "kind": "form", "purpose": "assessment", "name": "Data engineering screen", "status": "active", "version": 1, "source": "ai", "config": { "passMark": 70, "questions": [ { "id": "rtw", "type": "knockout", "prompt": "Do you have the right to work in Indonesia?", "knockoutAnswer": "yes", "required": true, "weight": 0 }, { "id": "backfill", "type": "single_choice", "prompt": "A daily DAG failed for 3 days and upstream data is now fixed. What do you do?", "options": [ { "id": "o1", "label": "Clear the failed runs and backfill those dates", "correct": true }, { "id": "o2", "label": "Trigger one run for today" } ], "required": true, "weight": 1 } ] }, "estimate": { "perCandidate": 2, "maxPerCandidate": 2, "lines": [ "1 written answer scored by Kumo · 2 credits per candidate" ] } } } }
POST/api/v1/ats/assessment-templates/draftats:assessments:manage

Draft an assessment with Kumo AI

Kumo writes a screening form (scenario questions at the job’s seniority, answer keys double-checked, debatable questions removed) or an AI interview plan for a job and stage. Nothing is saved — pass the result to `save_assessment_template`. Metered (about 3 credits for a form, 2 for an interview plan).

ParameterTypeDescription
kindrequiredstringform or ai_interview. One of: form, ai_interview.
job_idstringThe job to write it for.
stagestringThe stage it is for.
instructionsstringWhat to focus on, in plain words.
currentobjectAn existing config to improve instead of starting fresh.
curl -X POST https://kumohr.com/api/v1/ats/assessment-templates/draft \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"kind":"2026-09-07"}'
// 2xx response { "data": { "name": "Data engineering screen", "config": { "passMark": 70, "questions": [ { "id": "rtw", "type": "knockout", "prompt": "Do you have the right to work in Indonesia?", "knockoutAnswer": "yes", "required": true, "weight": 0 }, { "id": "backfill", "type": "single_choice", "prompt": "A daily DAG failed for 3 days and upstream data is now fixed. What do you do?", "options": [ { "id": "o1", "label": "Clear the failed runs and backfill those dates", "correct": true }, { "id": "o2", "label": "Trigger one run for today" } ], "required": true, "weight": 1 } ] }, "issues": [], "credits_charged": 3 } }
POST/api/v1/ats/assessments/sendats:assessments:send

Send a Kumo assessment to a candidate

Send a screening form or a Kumo AI interview (text or voice) for one application. Returns the candidate’s private link so you can deliver it yourself (`send_email: false`), or Kumo emails it in the workspace’s branding. Idempotent while the invite is open; `rotate_link: true` issues a fresh link. Someone who already completed it is not asked again (`already_done: true`) unless you pass `retake: true`. The result arrives as `ats.assessment.recorded` and routes like any assessment. Refused with 402 when the workspace is out of credits (written answers and interviews are scored by AI).

ParameterTypeDescription
application_idrequiredstringThe application.
template_idrequiredstringThe assessment template.
send_emailbooleanKumo emails the link (default true).
expires_in_daysnumber1-21 (default 7).
rotate_linkbooleanIssue a new link for an open invite.
retakebooleanSend again to someone who already completed it (default false).
curl -X POST https://kumohr.com/api/v1/ats/assessments/send \ -H "Authorization: Bearer kumo_…" \ -H "Content-Type: application/json" \ -d '{"application_id":"2026-09-07","template_id":"2026-09-07"}'
// 2xx response { "data": { "assessment_id": "uuid", "created": true, "link": "https://app.kumohr.com/assess/…", "expires_at": "2026-10-08T10:00:00.000Z", "emailed": false, "estimated_credits": 2 } }
Protocol, errors, and limits

When things go wrong

REST failures use the { error: { code, message } } envelope. Over MCP the same failures come back as a tool result with isError set, so your agent can read the reason rather than crashing. GET https://kumohr.com/api/mcp returns 405; this server is POST-only. Unauthenticated calls return 401 with WWW-Authenticate.

401Missing, invalid, revoked, or expired token
403Token is missing the required scope, or the user's role lacks access
429Over 60 requests per minute for this token; see the Retry-After header

Every REST response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset, so you can back off before you get there.

Kumo API & MCP: connect your agents to HR