Embed Kumo
Use the Agent API and Agent Kit SDK. Kumo's model plans and runs the work.
Agent quickstart →Embed Kumo's complete agent, call individual HR operations, connect another agent over MCP, or let Kumo act through tools you publish. This is the full public contract, with runnable requests and real response shapes.
const kumo = new KumoAgentClient({
token: delegated.accessToken
});
const thread = await kumo.createThread({
title: "New-starter readiness"
});
const result = await kumo.sendMessage(
thread.data.thread.id,
{ message: "Check Monday's starters." }
);
for await (const event of kumo.streamRun(
result.data.runId
)) {
console.log(event);
}All Kumo surfaces share one identity and governance model. Every request is attached to a named Kumo user. Scopes can narrow that user's live role; they can never widen it.
Use the Agent API and Agent Kit SDK. Kumo's model plans and runs the work.
Agent quickstart →Use versioned REST endpoints for focused, permission-aware HR operations.
REST reference →Point Cursor, Claude Code, VS Code, or your own MCP client at Kumo.
Inbound MCP →Register a published MCP server or run the outbound Connector privately.
Outbound MCP →Create an admin integration credential under Settings → API access. Keep it on your backend, exchange it for a short-lived named-user credential, then create a thread and send work to Kumo.
Select Integration and grant only the scopes your application needs.
Exchange on your backend. The user must already belong to the Kumo workspace.
Threads hold conversational continuity across messages and runs.
Use SSE for live progress and reconnect from the last event id.
Show approval or co-sign cards instead of auto-accepting actions.
npm install @kumohr/agent-sdkimport { KumoAgentClient } from "@kumohr/agent-sdk";
const integration = new KumoAgentClient({
token: process.env.KUMO_INTEGRATION_CREDENTIAL!
});
const delegated = await integration.exchangeCredential({
userId: kumoUserId,
externalSessionId: yourSession.id,
scopes: [
"agent:run",
"agent:read",
"agent:approve",
"agent:artifacts"
]
});const kumo = new KumoAgentClient({
token: delegated.accessToken
});
const created = await kumo.createThread({
title: "New-starter readiness"
});
const result = await kumo.sendMessage(
created.data.thread.id,
{
message:
"Check Monday's starters and prepare missing actions."
}
);
if (result.data.kind === "run") {
for await (const event of kumo.streamRun(
result.data.runId
)) {
renderAgentEvent(event);
}
}The Agent Kit package is currently distributed during technical onboarding. The REST and MCP contracts are public and usable without the SDK.
Kumo never runs an anonymous service agent. Credentials are workspace-bound, revocable, expirable, and shown once.
kumo_REST, MCP, development, and user-owned integrations
30–365 dayskumo_app_Customer backend; exchanges for delegated credentials
30–365 dayskumo_dlg_Short-lived application session for one existing user
15 minutesAuthorization: Bearer kumo_YOUR_TOKENleave:readRead leave requests and balancesleave:writeCreate leave requestsreports:readRun read-only reportsats:readRead recruiting: jobs, candidates, applications, pipeline stagesats:writeAdd candidates, create applications, move applications between stagesats:assessments:writePost assessment results (AI interviews, tests) back to applicationswebhooks:manageSubscribe to event webhooks and send test eventsats:transcripts:readRead interview transcripts (audit-logged)ats:jobs:writeCreate, draft (with Kumo AI) and publish jobs; set hiring stagesats:assessments:manageCreate and draft (with Kumo AI) screening forms and AI interview plansats:assessments:sendSend Kumo screening assessments and AI interviews to candidatesagent:runStart and continue Kumo agent workagent:readRead agent conversations, runs, and eventsagent:approveApprove or decline agent actionsagent:artifactsUpload inputs and retrieve agent deliverablesaudit:readRead and export the agent audit trailA scope grants no role permission by itself. Kumo resolves the user's current workspace role on every call, then intersects it with the credential scopes.
https://kumohr.comAll versioned REST routes begin with /api/v1.
{ data, requestId }Failures return { error: { code, message } }.
Idempotency-KeyRequired on Agent API mutations. Reuse only for an identical request.
auditCorrelationIdJoins the external request to model, approval, tool, receipt, and outcome events.
60/minutePer credential. Respect Retry-After on HTTP 429.
ISO 8601 · opaque IDsTimestamps are UTC. Treat every identifier as an opaque string.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 42
Retry-After: 42 # only on 429Kumo reads the user request and owned attachments.
The model proposes a durable, permission-aware run.
A person starts the plan and decides every committed action.
Native and connected tools run with idempotency and attribution.
Reports, tables, charts, and files remain attached to the run.
The full trace is encrypted, correlated, hash-chained, and exportable.
draftawaiting_inputawaiting_approvalrunningawaiting_externalcompletedA run may finish as failed or cancelled from any non-terminal stage. Parked external work resumes from a durable checkpoint.
Search the complete Agent API and focused REST surface. Expand an operation for parameters, behavioral notes, a runnable cURL request, and a real response shape.
Issue a short-lived credential delegated to an existing named Kumo user. The user must already belong to the integration credential’s workspace.
curl -X POST "https://kumohr.com/api/v1/agent/token" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"userId": "usr_8f2b…",
"externalSessionId": "portal-session-01839",
"scopes": [
"agent:run",
"agent:read",
"agent:approve",
"agent:artifacts"
]
}'{
"tokenType": "Bearer",
"accessToken": "kumo_dlg_…",
"expiresAt": "2026-08-21T10:30:00.000Z",
"userId": "usr_8f2b…",
"tenantId": "tenant_42ac…",
"scopes": [
"agent:run",
"agent:read",
"agent:approve",
"agent:artifacts"
],
"auditCorrelationId": "2b90…"
}List the delegated user’s Kumo conversations, newest activity first.
curl "https://kumohr.com/api/v1/agent/threads" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"threads": [
{
"id": "thread_19d0…",
"title": "New-starter readiness",
"createdAt": "2026-08-21T09:00:00.000Z",
"updatedAt": "2026-08-21T09:08:00.000Z",
"lastRun": {
"id": "run_e75f…",
"title": "Check new starters",
"status": "completed"
}
}
]
},
"requestId": "req_…"
}Create a conversation for embedded Kumo work.
curl -X POST "https://kumohr.com/api/v1/agent/threads" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"title": "New-starter readiness"
}'{
"data": {
"thread": {
"id": "thread_19d0…",
"title": "New-starter readiness",
"createdAt": "2026-08-21T09:00:00.000Z",
"updatedAt": "2026-08-21T09:00:00.000Z",
"lastRun": null
}
},
"requestId": "req_…",
"auditCorrelationId": "88a1…"
}Read a conversation and its ordered user, assistant, and run turns.
curl "https://kumohr.com/api/v1/agent/threads/RESOURCE_ID" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"thread": {
"id": "thread_19d0…",
"title": "New-starter readiness"
},
"turns": [
{
"id": "turn_1",
"role": "user",
"content": "Check whether Monday’s starters are ready.",
"runId": null,
"createdAt": "2026-08-21T09:01:00.000Z"
},
{
"id": "turn_2",
"role": "run",
"content": null,
"runId": "run_e75f…",
"run": {
"id": "run_e75f…",
"title": "Check new starters",
"status": "completed"
}
}
]
},
"requestId": "req_…"
}Let Kumo reply conversationally or plan a durable agent run using Kumo’s model runtime.
curl -X POST "https://kumohr.com/api/v1/agent/threads/RESOURCE_ID/messages" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"message": "Check whether Monday’s starters are ready and prepare the missing actions.",
"attachmentIds": [
"attachment_c32d…"
]
}'{
"data": {
"kind": "run",
"runId": "run_e75f…",
"detail": {
"run": {
"id": "run_e75f…",
"status": "awaiting_approval"
},
"steps": [
{
"id": "step_1",
"title": "Read upcoming starters",
"status": "pending"
}
],
"actions": [],
"artifacts": [],
"auditCorrelationId": "bc13…"
}
},
"requestId": "req_…"
}Read the plan, events, pending actions, and artifacts for one owned run.
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"run": {
"id": "run_e75f…",
"title": "Check new starters",
"status": "awaiting_approval",
"createdAt": "2026-08-21T09:01:00.000Z"
},
"steps": [
{
"id": "step_1",
"title": "Read upcoming starters",
"status": "pending"
}
],
"events": [
{
"id": "18201",
"type": "plan_proposed",
"payload": {
"stepCount": 3
}
}
],
"actions": [],
"artifacts": []
},
"requestId": "req_…"
}Open a resumable Server-Sent Events stream. Use the SSE `id` or `?cursor=` value to continue without losing events.
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/events" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"id: 18202\nevent: agent.event\ndata: {"id":"18202","type":"step_started","payload":{"title":"Read upcoming starters"}}\n\nRecord the named user’s plan decision and start or cancel the run.
curl -X POST "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/decisions" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"decision": "approve"
}'{
"data": {
"runId": "run_e75f…",
"decision": "approve",
"status": "running"
},
"requestId": "req_…",
"auditCorrelationId": "e21d…"
}Answer a clarification and resume planning or execution.
curl -X POST "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/answers" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"answer": "Use the London onboarding template."
}'{
"data": {
"runId": "run_e75f…",
"status": "awaiting_approval"
},
"requestId": "req_…",
"auditCorrelationId": "39fe…"
}Cancel a non-terminal run and preserve the reason in the audit ledger.
curl -X POST "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/cancel" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"reason": "The source data is being corrected."
}'{
"data": {
"runId": "run_e75f…",
"status": "cancelled"
},
"requestId": "req_…",
"auditCorrelationId": "72ac…"
}Approve, decline, retry, interrupt, or co-sign a pending action.
curl -X POST "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/actions/ACTION_ID/decisions" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"decision": "approve"
}'{
"data": {
"actionId": "action_4be2…",
"decision": "approve",
"status": "done",
"summary": "Manager nudges prepared."
},
"requestId": "req_…",
"auditCorrelationId": "adc1…"
}Upload and extract a file for the next Kumo message using multipart form data.
curl -X POST "https://kumohr.com/api/v1/agent/attachments" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-F "file=@document.pdf"{
"data": {
"attachment": {
"id": "attachment_c32d…",
"name": "onboarding-plan.pdf",
"mime": "application/pdf",
"sizeBytes": 84211,
"status": "ready",
"summary": "Onboarding plan for the London cohort."
}
},
"requestId": "req_…",
"auditCorrelationId": "a67b…"
}List deliverables produced for the delegated user. Use `?limit=1..100`.
curl "https://kumohr.com/api/v1/agent/artifacts" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"artifacts": [
{
"runId": "run_e75f…",
"runTitle": "Check new starters",
"artifact": {
"id": "artifact_41dd…",
"kind": "summary",
"title": "New-starter readiness",
"createdAt": "2026-08-21T09:07:00.000Z"
}
}
]
},
"requestId": "req_…"
}Retrieve one owned run artifact and its structured body.
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/artifacts/ARTIFACT_ID" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"artifact": {
"id": "artifact_41dd…",
"kind": "summary",
"title": "New-starter readiness",
"body": {
"markdown": "# Readiness\\n\\nThree starters are ready; one needs equipment."
}
}
},
"requestId": "req_…"
}Download an owned deliverable as Markdown, CSV, or JSON.
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/artifacts/ARTIFACT_ID/download" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"Binary response with Content-Disposition attachmentRead decrypted tenant-visible events with request, run, action, tool, and integrity correlation.
curl "https://kumohr.com/api/v1/agent/audit" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"events": [
{
"id": "291",
"sequence": "84",
"eventType": "agent.action.approve",
"outcome": "success",
"requestId": "req_…",
"runId": "run_e75f…",
"actionId": "action_4be2…",
"eventHash": "adc1…",
"occurredAt": "2026-08-21T09:06:00.000Z"
}
],
"nextCursor": "84"
},
"requestId": "req_…"
}Admin-only signed manifest for the tenant’s hash-chained ledger.
curl "https://kumohr.com/api/v1/agent/audit/export" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"manifest": {
"version": 1,
"tenantId": "tenant_42ac…",
"eventCount": 84,
"rootHash": "f19a…",
"integrity": {
"ok": true,
"brokenAt": null
}
},
"signature": "HMAC-SHA256 signature",
"algorithm": "HMAC-SHA256"
}Verify a signed manifest against Kumo’s audit signing key.
curl -X POST "https://kumohr.com/api/v1/agent/audit/verify" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"manifest": {
"version": 1,
"tenantId": "tenant_42ac…"
},
"signature": "…"
}'{
"data": {
"valid": true,
"tenantId": "tenant_42ac…",
"checkedAt": "2026-08-21T09:10:00.000Z"
},
"requestId": "req_…"
}List event endpoints, recent delivery attempts, and supported event names.
curl "https://kumohr.com/api/v1/agent/webhooks" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"endpoints": [
{
"id": "webhook_a10c…",
"label": "Customer portal",
"url": "https://example.com/kumo/events",
"enabled": true,
"events": [
"agent.run.completed",
"agent.action.required"
]
}
],
"deliveries": [],
"supportedEvents": [
"agent.run.completed",
"agent.run.failed",
"agent.run.cancelled",
"agent.action.required",
"agent.artifact.created",
"agent.connector.health"
]
},
"requestId": "req_…"
}Create a signed HTTPS event endpoint. The signing secret is returned once.
curl -X POST "https://kumohr.com/api/v1/agent/webhooks" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"label": "Customer portal",
"url": "https://example.com/kumo/events",
"events": [
"agent.run.completed",
"agent.action.required"
]
}'{
"data": {
"endpoint": {
"id": "webhook_a10c…",
"label": "Customer portal",
"url": "https://example.com/kumo/events",
"events": [
"agent.run.completed",
"agent.action.required"
],
"enabled": true
},
"signingSecret": "kumo_wh_…"
},
"requestId": "req_…",
"auditCorrelationId": "eb91…"
}Stop future deliveries without deleting delivery history.
curl -X DELETE "https://kumohr.com/api/v1/agent/webhooks/RESOURCE_ID" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Idempotency-Key: unique-request-id"{
"data": {
"id": "webhook_a10c…",
"enabled": false
},
"requestId": "req_…"
}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_YOUR_TOKEN"{
"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
}
}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.
statusquerystringFilter by status. Values: PENDING, APPROVED, REJECTED, CANCELLED, IN_PROGRESS, COMPLETED.
fromquerystringOnly requests ending on or after this date (YYYY-MM-DD).
toquerystringOnly requests starting on or before this date (YYYY-MM-DD).
limitquerynumberMax rows to return (1–200, default 50).
curl "https://kumohr.com/api/v1/leave/requests" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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"
}
]
}
}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.
start_datebodyrequiredstringFirst day off (YYYY-MM-DD).
end_datebodystringLast day off, inclusive (YYYY-MM-DD). Use this OR days.
daysbodynumberNumber of consecutive calendar days off. Use this OR end_date.
policybodystringLeave policy name, e.g. "Annual Leave". Defaults to the annual policy.
policy_idbodystringExact policy id (overrides policy).
reasonbodystringOptional short reason.
half_daybodybooleanTrue for a single half-day request.
curl -X POST "https://kumohr.com/api/v1/leave/requests" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"start_date": "2026-09-07",
"end_date": "2026-09-07",
"days": 2,
"policy": "Annual Leave",
"policy_id": "uuid",
"reason": "Family trip",
"half_day": false
}'{
"data": {
"id": "uuid",
"status": "PENDING",
"policy": "Annual Leave",
"start_date": "2026-09-07",
"end_date": "2026-09-08",
"total_days": 2,
"warnings": []
}
}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.
yearquerynumberBalance year (defaults to the current year).
min_untaken_daysquerynumberOnly employees with at least this many untaken days.
policyquerystringLeave policy name filter (defaults to annual policies).
curl "https://kumohr.com/api/v1/reports/untaken-leave" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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
}
}
}Jobs (requisitions) in the workspace that this token can see. Managers with assigned-only recruiting access see only the jobs they work on.
statusquerystringFilter by status, e.g. ACTIVE, DRAFT, CLOSED. Default: all.
searchquerystringFree-text search over title and requisition code.
pagequerynumberPage number, from 1.
limitquerynumberRows per page (1–100, default 25).
curl "https://kumohr.com/api/v1/ats/jobs" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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
}
}
}Candidates in the talent pool, with their applications. Pass `email` for an exact lookup (the way to check whether a candidate already exists).
emailquerystringExact email match.
searchquerystringFree-text search over name, email and title.
stagequerystringOnly candidates currently in this stage.
statusquerystringCandidate status, e.g. NEW, ACTIVE, HIRED, REJECTED.
pagequerynumberPage number, from 1.
limitquerynumberRows per page (1–100, default 25).
curl "https://kumohr.com/api/v1/ats/candidates" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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
}
}
}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).
first_namebodyrequiredstringFirst name.
last_namebodyrequiredstringLast name.
emailbodyrequiredstringEmail address — the dedupe key.
phonebodystringPhone number, with country code.
locationbodystringCity and/or country.
current_titlebodystringCurrent job title.
current_companybodystringCurrent employer.
linkedin_urlbodystringLinkedIn profile URL.
skillsbodystringSkills, comma-separated (or a JSON array of strings).
tagsbodystringTags, comma-separated (or a JSON array of strings).
notesbodystringFree-text recruiter notes.
sourcebodystringWhere the candidate came from, shown to recruiters. Default: the source_system name, or API.
source_systembodystringYour system, e.g. "greenhouse" or "client-ats". With external_id, the match key.
external_idbodystringThe candidate’s id in your system. Sending the same one again updates the same candidate.
resume_urlbodystringhttps link to the CV (PDF or Word, up to 10 MB). Kumo downloads it once.
resume_base64bodystringThe 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_filenamebodystringFile name for the CV, e.g. "jane-doe.pdf".
job_idbodystringAlso apply the candidate to this job.
curl -X POST "https://kumohr.com/api/v1/ats/candidates" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"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": "https://www.linkedin.com/in/janedoe",
"skills": "Python, Django",
"tags": "referral",
"notes": "Referred by the engineering team.",
"source": "your-product",
"source_system": "value",
"external_id": "session-f47ac10b",
"resume_url": "value",
"resume_base64": "value",
"resume_filename": "value",
"job_id": "uuid"
}'{
"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"
}
}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.
candidatesbodyrequiredarrayThe candidates: the same fields as “Add a candidate”.
job_idbodystringApply every candidate to this job.
source_systembodystringYour system, for every candidate.
sourcebodystringWhere they came from, for every candidate.
curl -X POST "https://kumohr.com/api/v1/ats/candidates/import" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"candidates": "2026-09-07",
"job_id": "uuid",
"source_system": "value",
"source": "your-product"
}'{
"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
}
}
}Applications — a candidate applied to a job — with their current pipeline stage. Filter by job to read a job’s board.
job_idquerystringOnly applications to this job.
candidate_idquerystringOnly this candidate’s applications.
stagequerystringOnly applications in this stage.
statusquerystringApplication status, e.g. NEW, INTERVIEW, OFFER, HIRED, REJECTED.
pagequerynumberPage number, from 1.
limitquerynumberRows per page (1–100, default 25).
curl "https://kumohr.com/api/v1/ats/applications" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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
}
}
}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.
candidate_idbodyrequiredstringThe candidate.
job_idbodyrequiredstringThe job.
stagebodystringPipeline stage to place the application in. See list_ats_pipeline_stages.
sourcebodystringWhere the application came from. Default: API.
curl -X POST "https://kumohr.com/api/v1/ats/applications" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"candidate_id": "uuid",
"job_id": "uuid",
"stage": "AI Interview",
"source": "your-product"
}'{
"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
}
}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.
application_idbodyrequiredstringThe application to move.
stagebodyrequiredstringTarget stage name (case-insensitive).
reasonbodystringOptional note for the activity log.
curl -X POST "https://kumohr.com/api/v1/ats/applications/move" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"application_id": "uuid",
"stage": "AI Interview",
"reason": "Family trip"
}'{
"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"
}
}
}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.
job_idquerystringThe job whose pipeline to read.
curl "https://kumohr.com/api/v1/ats/pipeline/stages" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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"
}
]
}
}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.
querybodystringWho you are looking for, in plain words.
job_idbodystringRank the pool for this job (its criteria and stored fit scores).
required_skillsbodyarraySkills every result must have (up to 3).
preferred_skillsbodyarraySkills that rank people higher (up to 8).
locationsbodyarrayCities, regions or countries.
min_yearsbodynumberMinimum years of experience (people with no recorded experience are kept, ranked lower).
filtersbodyobjectHard filters: {skills, locations, minYears, maxYears, sources, tags, appliedToJobId, hasCv, activeWithinDays}.
modebodystring`ai` (default) parses the query with a model; `basic` never calls a model. Values: ai, basic.
explainbodybooleanAdd a one-line reason and a profile quote per result (metered).
limitbodynumberResults to return, 1-50 (default 20).
curl -X POST "https://kumohr.com/api/v1/ats/candidates/search" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"query": "value",
"job_id": "uuid",
"required_skills": "value",
"preferred_skills": "value",
"locations": "value",
"min_years": 1,
"filters": {
"stages": [
"AI Interview"
]
},
"mode": "value",
"explain": false,
"limit": 1
}'{
"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
}
]
}
}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.
application_idbodyrequiredstringThe application (from the webhook payload).
providerbodyrequiredstringYour product key, e.g. "genia". Stable across calls.
external_idbodyrequiredstringYour id for this session or test.
statusbodyrequiredstringWhere the assessment is. Values: pending, invited, in_progress, completed, expired, declined, cancelled, error.
scorebodynumberOverall score, 0–100.
passedbodybooleanYour recommendation. Omit to let the route’s minimum score decide.
summarybodystringConclusion shown to recruiters.
strengthsbodystringStrengths.
weaknessesbodystringWeaknesses / concerns.
report_urlbodystringhttps link to the full report or recording.
rawbodyobjectYour full result as JSON (up to 256 KB), kept for recruiters.
recommendationbodystringYour recommendation for the recruiter. Values: advance, hold, reject.
confidencebodynumberHow sure you are, 0–1.
modebodystringHow the interview ran. Values: voice, text.
disclosurebodyobjectWhat 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_humanbodybooleanWith status "declined": the candidate asked to speak with a person. Never scored; the hiring team is told.
sentimentbodyobjectYour 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.
kindbodystringai_interview (default), skills_test, async_video, reference_check or custom.
dimension_scoresbodyarrayPer-dimension scores shown as bars: [{key, label, score 0-100, weight?, evidence: [{quote, t_ms?}]}] (up to 20).
transcriptbodyarrayThe 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_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"application_id": "uuid",
"provider": "your-product",
"external_id": "session-f47ac10b",
"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.your-product.com/sessions/f47ac10b",
"raw": {
"rank": "3/27"
},
"recommendation": "value",
"confidence": 1,
"mode": "value",
"disclosure": {},
"preferred_human": false,
"sentiment": {},
"kind": "value",
"dimension_scores": "value",
"transcript": "value"
}'{
"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"
}
}
}Assessment results recorded for an application or candidate, newest first.
application_idquerystringResults for this application.
candidate_idquerystringResults for this candidate.
curl "https://kumohr.com/api/v1/ats/assessments" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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"
}
]
}
}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_YOUR_TOKEN"{
"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
}
]
}
}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.
trigger_stagebodyrequiredstringThe stage the assessment runs in, e.g. "AI Interview".
pass_stagebodyrequiredstringWhere a pass goes.
fail_stagebodyrequiredstringWhere a fail goes.
min_scorebodynumberPass mark (0–100) used when a result has a score but no `passed`.
job_idbodystringOnly for this job.
providerbodystringOnly for results from this provider.
fail_policybodystringOn 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). Values: move_and_review, move, review.
curl -X POST "https://kumohr.com/api/v1/ats/assessment-routes" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"trigger_stage": "AI Interview",
"pass_stage": "Passed Interview",
"fail_stage": "Failed Interview",
"min_score": 70,
"job_id": "uuid",
"provider": "your-product",
"fail_policy": "value"
}'{
"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
}
}
}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.
candidate_idqueryrequiredstringThe candidate.
curl "https://kumohr.com/api/v1/ats/candidates/resume" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"data": {
"candidate_id": "uuid",
"url": "https://…/resumes/…/jane-doe.pdf?token=…",
"filename": "jane-doe.pdf",
"expires_at": "2026-09-25T10:15:00.000Z"
}
}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).
idqueryrequiredstringThe assessment id.
includequerystringComma-separated extras: transcript.
curl "https://kumohr.com/api/v1/ats/assessments/detail" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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"
}
}
}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.
idbodyrequiredstringThe assessment id.
decisionbodyrequiredstringThe decision. Values: advance, hold, reject.
reasonbodystringWhy (required when overriding the recommendation).
curl -X POST "https://kumohr.com/api/v1/ats/assessments/decision" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"id": "uuid",
"decision": "value",
"reason": "Family trip"
}'{
"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"
}
}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.
job_idqueryrequiredstringThe job.
curl "https://kumohr.com/api/v1/ats/jobs/criteria" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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": []
}
]
}
}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.
job_idbodyrequiredstringThe job.
criteriabodyrequiredarrayList of {label, category?, importance?, weight?, knockout?, min_years?, evidence_hint?, lint_justification?}.
match_thresholdbodynumberJob-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_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"job_id": "uuid",
"criteria": "value",
"match_threshold": 1
}'{
"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": []
}
]
}
}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.
job_idbodyrequiredstringThe job.
curl -X POST "https://kumohr.com/api/v1/ats/jobs/match-pool" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"job_id": "uuid"
}'{
"data": {
"run_id": "uuid",
"pool_size": 142,
"already_scored": 38,
"estimated_credits": 208
}
}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.
job_idqueryrequiredstringThe job.
curl "https://kumohr.com/api/v1/ats/jobs/match-pool" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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
}
]
}
}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.
job_idbodyrequiredstringThe job.
candidate_idsbodyrequiredarrayCandidates to place (up to 500).
curl -X POST "https://kumohr.com/api/v1/ats/jobs/match-pool/place" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"job_id": "uuid",
"candidate_ids": "2026-09-07"
}'{
"data": {
"placed": 12,
"already_applied": 1
}
}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.
briefbodyrequiredstringWhat you are hiring for, in your own words (any language).
answersbodyobjectAnswers to earlier follow-ups: {title, seniority, where, salary}.
curl -X POST "https://kumohr.com/api/v1/ats/jobs/draft" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"brief": "value",
"answers": {}
}'{
"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"
]
}
}
}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.
draftbodyobjectA draft from draft_job_with_ai (edited as you like).
titlebodystringWithout a draft: the job title.
departmentbodystringWithout a draft: the department.
locationbodystringCity or office (required unless remote).
work_arrangementbodystringOnsite, Hybrid or Remote. Values: Onsite, Hybrid, Remote.
descriptionbodystringThe job ad (HTML or text).
stagesbodyarrayHiring stages: [{name, objective?, assessment?: {type: none|form|ai_interview|template|partner, …}, pass_stage?, fail_policy?}].
requisition_codebodystringYour own requisition number (e.g. from your HR system). Omit and Kumo numbers it (REQ-2026-0042). Unique per workspace.
publishbodybooleanPublish immediately (subject to approval).
curl -X POST "https://kumohr.com/api/v1/ats/jobs" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"draft": {},
"title": "value",
"department": "value",
"location": "Jakarta",
"work_arrangement": "value",
"description": "value",
"stages": "value",
"requisition_code": "value",
"publish": false
}'{
"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"
}
]
}
}
}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.
job_idbodyrequiredstringThe job.
curl -X POST "https://kumohr.com/api/v1/ats/jobs/publish" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"job_id": "uuid"
}'{
"data": {
"job": {
"id": "uuid",
"status": "ACTIVE"
},
"already_live": false
}
}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.
job_idbodyrequiredstringThe job.
stagesbodyrequiredarray[{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_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"job_id": "uuid",
"stages": "value"
}'{
"data": {
"stages": [
{
"id": "uuid",
"name": "Screening"
}
],
"renamed": [],
"deactivated": [],
"assessments": [
{
"stage": "Screening",
"templateId": "uuid",
"automationId": "uuid"
}
]
}
}Screening forms and AI interview plans — a job’s own plus the workspace library — with the credit cost per candidate.
job_idquerystringOnly this job’s templates (and the library).
kindquerystringform or ai_interview. Values: form, ai_interview.
curl "https://kumohr.com/api/v1/ats/assessment-templates" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"{
"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"
]
}
}
]
}
}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).
idbodystringUpdate this template.
job_idbodystringAttach to a job (omit for the workspace library).
kindbodystringform or ai_interview. Values: form, ai_interview.
namebodyrequiredstringName shown to candidates.
configbodyrequiredobjectThe questions or interview plan (see the example).
statusbodystringdraft or active. Values: draft, active.
curl -X POST "https://kumohr.com/api/v1/ats/assessment-templates" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"id": "uuid",
"job_id": "uuid",
"kind": "value",
"name": "value",
"config": {},
"status": "completed"
}'{
"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"
]
}
}
}
}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).
kindbodyrequiredstringform or ai_interview. Values: form, ai_interview.
job_idbodystringThe job to write it for.
stagebodystringThe stage it is for.
instructionsbodystringWhat to focus on, in plain words.
currentbodyobjectAn existing config to improve instead of starting fresh.
curl -X POST "https://kumohr.com/api/v1/ats/assessment-templates/draft" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"kind": "value",
"job_id": "uuid",
"stage": "AI Interview",
"instructions": "value",
"current": {}
}'{
"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
}
}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).
application_idbodyrequiredstringThe application.
template_idbodyrequiredstringThe assessment template.
send_emailbodybooleanKumo emails the link (default true).
expires_in_daysbodynumber1-21 (default 7).
rotate_linkbodybooleanIssue a new link for an open invite.
retakebodybooleanSend again to someone who already completed it (default false).
curl -X POST "https://kumohr.com/api/v1/ats/assessments/send" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"application_id": "uuid",
"template_id": "uuid",
"send_email": false,
"expires_in_days": 1,
"rotate_link": false,
"retake": false
}'{
"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
}
}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_YOUR_TOKEN"{
"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"
]
}
}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.
urlbodyrequiredstringYour https endpoint.
eventsbodyrequiredstringEvent 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.
filtersbodyobjectOptional: {"stages": [stage names], "job_ids": [job ids]}.
labelbodystringA name for this endpoint.
curl -X POST "https://kumohr.com/api/v1/webhooks" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"url": "https://api.your-product.com/kumo/events",
"events": "ats.application.stage_entered",
"filters": {
"stages": [
"AI Interview"
]
},
"label": "AI interviews"
}'{
"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_…"
}
}Stops deliveries to an endpoint. Pending deliveries to it are dropped.
idqueryrequiredstringThe webhook id.
curl -X DELETE "https://kumohr.com/api/v1/webhooks" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Idempotency-Key: unique-request-id"{
"data": {
"id": "uuid",
"enabled": false
}
}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.
idbodyrequiredstringThe webhook id.
curl -X POST "https://kumohr.com/api/v1/webhooks/test" \
-H "Authorization: Bearer kumo_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id" \
--data '{
"id": "uuid"
}'{
"data": {
"event_id": "evt_…",
"delivered": true,
"status": "delivered",
"response_status": 200,
"error": null
}
}Stream /api/v1/agent/runs/{id}/events. Kumo sends standard SSE ids so a dropped client can continue from its final durable event.
curl -N "https://kumohr.com/api/v1/agent/runs/RUN_ID/events?cursor=18201" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"event: status
data: {"status":"running","requestId":"req_..."}
id: 18202
event: agent.event
data: {"id":"18202","type":"step_started","payload":{"title":"Read upcoming starters"}}
event: heartbeat
data: {"cursor":"18202"}
event: done
data: {"cursor":"18209"}statusCurrent persisted run statusagent.eventPlan, tool, action, artifact, warning, or outcomeheartbeatConnection liveness and current cursordoneTerminal run; close the streamerrorTransport or data-source failureSSE is the interactive channel. Webhooks notify your backend when work needs attention, reaches a durable outcome, or a recruiting record changes. Delivery is signed and retried with backoff. Manage endpoints with /api/v1/webhooks, and narrow them by pipeline stage or job with filters. See the recruiting integration guide.
agent.run.completedagent.run.failedagent.run.cancelledagent.action.requiredagent.artifact.createdagent.connector.healthats.application.stage_enteredats.application.createdats.assessment.recordedats.assessment.invitedats.assessment.startedats.assessment.declinedats.job.createdats.job.publishedKumo-Webhook-Id: delivery_...
Kumo-Webhook-Timestamp: 1787290200
Kumo-Webhook-Signature: v1=7dc7...
# Signed input:
HMAC_SHA256(signing_secret, timestamp + "." + raw_body)const valid = await verifyWebhookSignature({
secret: process.env.KUMO_WEBHOOK_SECRET!,
timestamp: request.headers.get("Kumo-Webhook-Timestamp")!,
body: rawBody,
signature: request.headers.get("Kumo-Webhook-Signature")!,
toleranceSeconds: 300
});
if (!valid) return new Response("Invalid signature", { status: 401 });JSON re-serialization changes bytes and invalidates the signature. Verify before parsing.
Point an MCP client at https://kumohr.com/api/mcp. Tools are filtered to the PAT's scopes and the named user's role.
Register a published HTTPS MCP server or run the Connector inside your network. Every external action remains human-confirmed.
{
"mcpServers": {
"kumo-hr": {
"type": "http",
"url": "https://kumohr.com/api/mcp",
"headers": {
"Authorization": "Bearer kumo_YOUR_TOKEN"
}
}
}
}KUMO_URL=https://kumohr.com \
KUMO_CONNECTOR_TOKEN=kumo_cn_... \
MCP_URL=http://127.0.0.1:8799/mcp \
npm run mcp:connectorKumo correlates the credential, external session, message, model calls, plan, approvals, tools, MCP receipts, artifacts, credits, recovery, and final outcome.
Full-fidelity business payloads use AES-256-GCM after secret redaction.
Tenant sequence, previous hash, event hash, and server signature expose tampering.
Plan approval, action decisions, amendments, and co-signatures are normalized records.
Admins export a manifest and verify it through the Audit API.
curl "https://kumohr.com/api/v1/agent/audit?runId=RUN_ID&limit=100" \
-H "Authorization: Bearer kumo_YOUR_TOKEN"400Invalid requestMalformed fields, unsupported decision, or missing idempotency key.
401UnauthorizedMissing, unknown, revoked, expired, or invalid credential.
403ForbiddenMissing scope, live role denial, or admin-only operation.
404Not foundThe resource does not exist or is not owned by this named user.
409ConflictInvalid run state, duplicate in progress, or idempotency mismatch.
413File too largeThe attachment exceeds the accepted upload limit.
415Unsupported mediaKumo cannot extract that attachment type.
429Rate limitedWait for the number of seconds in Retry-After.
{
"error": {
"code": "idempotency_conflict",
"message": "That Idempotency-Key was already used for a different request."
},
"requestId": "req_..."
}We will map identities, system boundaries, named tools, approvals, and residency with your engineering team.