Kumo developer documentation

Build with the Kumo Agent Platform.

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.

agent.ts
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);
}
58 documented operations16 granular scopesResumable SSESigned webhooksMCP both ways
Overview

One platform, four ways to use it.

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.

01

Embed Kumo

Use the Agent API and Agent Kit SDK. Kumo's model plans and runs the work.

Agent quickstart →
02

Call Kumo

Use versioned REST endpoints for focused, permission-aware HR operations.

REST reference →
03

Connect an agent

Point Cursor, Claude Code, VS Code, or your own MCP client at Kumo.

Inbound MCP →
04

Connect a system

Register a published MCP server or run the outbound Connector privately.

Outbound MCP →
Quickstart

Run Kumo from your application.

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.

  1. 1
    Create credentials

    Select Integration and grant only the scopes your application needs.

  2. 2
    Delegate to a user

    Exchange on your backend. The user must already belong to the Kumo workspace.

  3. 3
    Create a thread

    Threads hold conversational continuity across messages and runs.

  4. 4
    Stream the run

    Use SSE for live progress and reconnect from the last event id.

  5. 5
    Present decisions

    Show approval or co-sign cards instead of auto-accepting actions.

Install
npm install @kumohr/agent-sdk
Server-side credential exchange
import { 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"
  ]
});
Run and stream
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);
  }
}
SDK distribution

The Agent Kit package is currently distributed during technical onboarding. The REST and MCP contracts are public and usable without the SDK.

Authentication

Credentials identify a real user.

Kumo never runs an anonymous service agent. Credentials are workspace-bound, revocable, expirable, and shown once.

CredentialPrefixUseLifetime
Personal access tokenkumo_

REST, MCP, development, and user-owned integrations

30–365 days
Integration credentialkumo_app_

Customer backend; exchanges for delegated credentials

30–365 days
Delegated credentialkumo_dlg_

Short-lived application session for one existing user

15 minutes

Send the credential

Authorization header
Authorization: Bearer kumo_YOUR_TOKEN

Available scopes

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
Permission rule

A 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.

API conventions

Predictable by default.

Base URL

https://kumohr.com

All versioned REST routes begin with /api/v1.

JSON envelope

{ data, requestId }

Failures return { error: { code, message } }.

Idempotency

Idempotency-Key

Required on Agent API mutations. Reuse only for an identical request.

Correlation

auditCorrelationId

Joins the external request to model, approval, tool, receipt, and outcome events.

Rate limit

60/minute

Per credential. Respect Retry-After on HTTP 429.

Time and IDs

ISO 8601 · opaque IDs

Timestamps are UTC. Treat every identifier as an opaque string.

Rate-limit headers

Response headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 42
Retry-After: 42  # only on 429
Agent lifecycle

From message to provable outcome.

01Message

Kumo reads the user request and owned attachments.

02Plan

The model proposes a durable, permission-aware run.

03Approval

A person starts the plan and decides every committed action.

04Execution

Native and connected tools run with idempotency and attribution.

05Artifact

Reports, tables, charts, and files remain attached to the run.

06Audit

The full trace is encrypted, correlated, hash-chained, and exportable.

Run statuses

draftawaiting_inputawaiting_approvalrunningawaiting_externalcompleted

A run may finish as failed or cancelled from any non-terminal stage. Parked external work resumes from a durable checkpoint.

API reference

Every public endpoint.

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.

Complete reference58 documented operations

Credentials

1
POST/api/v1/agent/tokenExchange an integration credentialagent:run

Issue a short-lived credential delegated to an existing named Kumo user. The user must already belong to the integration credential’s workspace.

Important

  • Call this from your backend. Never put an integration credential in browser code.
  • Requested scopes are intersected with the integration credential’s scopes.
Request
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"
  ]
}'
Response
{
  "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…"
}

Threads

4
GET/api/v1/agent/threadsList agent threadsagent:read

List the delegated user’s Kumo conversations, newest activity first.

Request
curl "https://kumohr.com/api/v1/agent/threads" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "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_…"
}
POST/api/v1/agent/threadsCreate an agent threadagent:run

Create a conversation for embedded Kumo work.

Request
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"
}'
Response
{
  "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…"
}
GET/api/v1/agent/threads/{id}Get an agent threadagent:read

Read a conversation and its ordered user, assistant, and run turns.

Request
curl "https://kumohr.com/api/v1/agent/threads/RESOURCE_ID" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "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_…"
}
POST/api/v1/agent/threads/{id}/messagesSend a message to Kumoagent:run

Let Kumo reply conversationally or plan a durable agent run using Kumo’s model runtime.

Important

  • A conversational answer returns HTTP 200 with `kind: reply`; a planned run returns HTTP 202.
Request
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…"
  ]
}'
Response
{
  "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_…"
}

Runs

5
GET/api/v1/agent/runs/{id}Get an agent runagent:read

Read the plan, events, pending actions, and artifacts for one owned run.

Request
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "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_…"
}
GET/api/v1/agent/runs/{id}/eventsStream agent run eventsagent:read

Open a resumable Server-Sent Events stream. Use the SSE `id` or `?cursor=` value to continue without losing events.

Important

  • Events include `status`, `agent.event`, `heartbeat`, `done`, and `error`.
  • Reconnect with `?cursor=<last-event-id>` after a transport interruption.
Connect
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/events" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Event stream
id: 18202\nevent: agent.event\ndata: {"id":"18202","type":"step_started","payload":{"title":"Read upcoming starters"}}\n\n
POST/api/v1/agent/runs/{id}/decisionsApprove or cancel a runagent:approve

Record the named user’s plan decision and start or cancel the run.

Important

  • Use `cancel` here before execution, or the dedicated cancel endpoint at any non-terminal stage.
Request
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"
}'
Response
{
  "data": {
    "runId": "run_e75f…",
    "decision": "approve",
    "status": "running"
  },
  "requestId": "req_…",
  "auditCorrelationId": "e21d…"
}
POST/api/v1/agent/runs/{id}/answersAnswer Kumoagent:run

Answer a clarification and resume planning or execution.

Request
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."
}'
Response
{
  "data": {
    "runId": "run_e75f…",
    "status": "awaiting_approval"
  },
  "requestId": "req_…",
  "auditCorrelationId": "39fe…"
}
POST/api/v1/agent/runs/{id}/cancelCancel an agent runagent:approve

Cancel a non-terminal run and preserve the reason in the audit ledger.

Request
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."
}'
Response
{
  "data": {
    "runId": "run_e75f…",
    "status": "cancelled"
  },
  "requestId": "req_…",
  "auditCorrelationId": "72ac…"
}

Actions

1
POST/api/v1/agent/runs/{id}/actions/{actionId}/decisionsDecide an agent actionagent:approve

Approve, decline, retry, interrupt, or co-sign a pending action.

Important

  • `cosign` requires a second, distinct elevated user.
  • External MCP actions always require explicit approval.
Request
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"
}'
Response
{
  "data": {
    "actionId": "action_4be2…",
    "decision": "approve",
    "status": "done",
    "summary": "Manager nudges prepared."
  },
  "requestId": "req_…",
  "auditCorrelationId": "adc1…"
}

Files

4
POST/api/v1/agent/attachmentsUpload an agent attachmentagent:artifacts

Upload and extract a file for the next Kumo message using multipart form data.

Important

  • Use the returned attachment id in `send_agent_message.attachmentIds`.
Request
curl -X POST "https://kumohr.com/api/v1/agent/attachments" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN" \
  -F "file=@document.pdf"
Response
{
  "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…"
}
GET/api/v1/agent/artifactsList agent artifactsagent:artifacts

List deliverables produced for the delegated user. Use `?limit=1..100`.

Request
curl "https://kumohr.com/api/v1/agent/artifacts" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "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_…"
}
GET/api/v1/agent/runs/{id}/artifacts/{artifactId}Get an agent artifactagent:artifacts

Retrieve one owned run artifact and its structured body.

Request
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/artifacts/ARTIFACT_ID" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "data": {
    "artifact": {
      "id": "artifact_41dd…",
      "kind": "summary",
      "title": "New-starter readiness",
      "body": {
        "markdown": "# Readiness\\n\\nThree starters are ready; one needs equipment."
      }
    }
  },
  "requestId": "req_…"
}
GET/api/v1/agent/runs/{id}/artifacts/{artifactId}/downloadDownload an agent artifactagent:artifacts

Download an owned deliverable as Markdown, CSV, or JSON.

Important

  • Use `?format=md`, `?format=csv`, or `?format=json`.
Request
curl "https://kumohr.com/api/v1/agent/runs/RESOURCE_ID/artifacts/ARTIFACT_ID/download" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
Binary response with Content-Disposition attachment

Audit

3
GET/api/v1/agent/auditRead the agent audit ledgeraudit:read

Read decrypted tenant-visible events with request, run, action, tool, and integrity correlation.

Important

  • Filter with `after`, `runId`, `actionId`, `requestId`, and `limit` (maximum 200).
Request
curl "https://kumohr.com/api/v1/agent/audit" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "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_…"
}
GET/api/v1/agent/audit/exportExport signed agent auditaudit:read

Admin-only signed manifest for the tenant’s hash-chained ledger.

Request
curl "https://kumohr.com/api/v1/agent/audit/export" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "manifest": {
    "version": 1,
    "tenantId": "tenant_42ac…",
    "eventCount": 84,
    "rootHash": "f19a…",
    "integrity": {
      "ok": true,
      "brokenAt": null
    }
  },
  "signature": "HMAC-SHA256 signature",
  "algorithm": "HMAC-SHA256"
}
POST/api/v1/agent/audit/verifyVerify an audit exportaudit:read

Verify a signed manifest against Kumo’s audit signing key.

Request
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": "…"
}'
Response
{
  "data": {
    "valid": true,
    "tenantId": "tenant_42ac…",
    "checkedAt": "2026-08-21T09:10:00.000Z"
  },
  "requestId": "req_…"
}

Webhooks

3
GET/api/v1/agent/webhooksList agent webhooksagent:read

List event endpoints, recent delivery attempts, and supported event names.

Request
curl "https://kumohr.com/api/v1/agent/webhooks" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Response
{
  "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_…"
}
POST/api/v1/agent/webhooksCreate an agent webhookagent:run

Create a signed HTTPS event endpoint. The signing secret is returned once.

Important

  • Store the signing secret immediately. Kumo never returns it again.
Request
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"
  ]
}'
Response
{
  "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…"
}
DELETE/api/v1/agent/webhooks/{id}Disable an agent webhookagent:run

Stop future deliveries without deleting delivery history.

Request
curl -X DELETE "https://kumohr.com/api/v1/agent/webhooks/RESOURCE_ID" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN" \
  -H "Idempotency-Key: unique-request-id"
Response
{
  "data": {
    "id": "webhook_a10c…",
    "enabled": false
  },
  "requestId": "req_…"
}

REST · Identity

1
GET/api/v1/meIntrospect the tokenany valid 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.

Request
curl "https://kumohr.com/api/v1/me" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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
  }
}

REST · Leave

2
GET/api/v1/leave/requestsList leave requestsleave:read

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.

Parameters

statusquerystring

Filter by status. Values: PENDING, APPROVED, REJECTED, CANCELLED, IN_PROGRESS, COMPLETED.

fromquerystring

Only requests ending on or after this date (YYYY-MM-DD).

toquerystring

Only requests starting on or before this date (YYYY-MM-DD).

limitquerynumber

Max rows to return (1–200, default 50).

Request
curl "https://kumohr.com/api/v1/leave/requests" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/requestsCreate a leave requestleave:write

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.

Parameters

start_datebodyrequiredstring

First day off (YYYY-MM-DD).

end_datebodystring

Last day off, inclusive (YYYY-MM-DD). Use this OR days.

daysbodynumber

Number of consecutive calendar days off. Use this OR end_date.

policybodystring

Leave policy name, e.g. "Annual Leave". Defaults to the annual policy.

policy_idbodystring

Exact policy id (overrides policy).

reasonbodystring

Optional short reason.

half_daybodyboolean

True for a single half-day request.

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
}'
Response
{
  "data": {
    "id": "uuid",
    "status": "PENDING",
    "policy": "Annual Leave",
    "start_date": "2026-09-07",
    "end_date": "2026-09-08",
    "total_days": 2,
    "warnings": []
  }
}

REST · Reports

1
GET/api/v1/reports/untaken-leaveUntaken leave reportreports:read

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.

Parameters

yearquerynumber

Balance year (defaults to the current year).

min_untaken_daysquerynumber

Only employees with at least this many untaken days.

policyquerystring

Leave policy name filter (defaults to annual policies).

Request
curl "https://kumohr.com/api/v1/reports/untaken-leave" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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
    }
  }
}

REST · Recruiting

29
GET/api/v1/ats/jobsList jobsats:read

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

Parameters

statusquerystring

Filter by status, e.g. ACTIVE, DRAFT, CLOSED. Default: all.

searchquerystring

Free-text search over title and requisition code.

pagequerynumber

Page number, from 1.

limitquerynumber

Rows per page (1–100, default 25).

Request
curl "https://kumohr.com/api/v1/ats/jobs" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/candidatesList or find candidatesats:read

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

Parameters

emailquerystring

Exact email match.

searchquerystring

Free-text search over name, email and title.

stagequerystring

Only candidates currently in this stage.

statusquerystring

Candidate status, e.g. NEW, ACTIVE, HIRED, REJECTED.

pagequerynumber

Page number, from 1.

limitquerynumber

Rows per page (1–100, default 25).

Request
curl "https://kumohr.com/api/v1/ats/candidates" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/candidatesAdd a candidateats:write

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).

Parameters

first_namebodyrequiredstring

First name.

last_namebodyrequiredstring

Last name.

emailbodyrequiredstring

Email address — the dedupe key.

phonebodystring

Phone number, with country code.

locationbodystring

City and/or country.

current_titlebodystring

Current job title.

current_companybodystring

Current employer.

linkedin_urlbodystring

LinkedIn profile URL.

skillsbodystring

Skills, comma-separated (or a JSON array of strings).

tagsbodystring

Tags, comma-separated (or a JSON array of strings).

notesbodystring

Free-text recruiter notes.

sourcebodystring

Where the candidate came from, shown to recruiters. Default: the source_system name, or API.

source_systembodystring

Your system, e.g. "greenhouse" or "client-ats". With external_id, the match key.

external_idbodystring

The candidate’s id in your system. Sending the same one again updates the same candidate.

resume_urlbodystring

https link to the CV (PDF or Word, up to 10 MB). Kumo downloads it once.

resume_base64bodystring

The 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_filenamebodystring

File name for the CV, e.g. "jane-doe.pdf".

job_idbodystring

Also apply the candidate to this job.

Request
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"
}'
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/importAdd or update many candidatesats:write

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.

Parameters

candidatesbodyrequiredarray

The candidates: the same fields as “Add a candidate”.

job_idbodystring

Apply every candidate to this job.

source_systembodystring

Your system, for every candidate.

sourcebodystring

Where they came from, for every candidate.

Request
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"
}'
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/applicationsList applicationsats:read

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

Parameters

job_idquerystring

Only applications to this job.

candidate_idquerystring

Only this candidate’s applications.

stagequerystring

Only applications in this stage.

statusquerystring

Application status, e.g. NEW, INTERVIEW, OFFER, HIRED, REJECTED.

pagequerynumber

Page number, from 1.

limitquerynumber

Rows per page (1–100, default 25).

Request
curl "https://kumohr.com/api/v1/ats/applications" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/applicationsApply a candidate to a jobats:write

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.

Parameters

candidate_idbodyrequiredstring

The candidate.

job_idbodyrequiredstring

The job.

stagebodystring

Pipeline stage to place the application in. See list_ats_pipeline_stages.

sourcebodystring

Where the application came from. Default: API.

Request
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"
}'
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/moveMove an application to a stageats:write

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.

Parameters

application_idbodyrequiredstring

The application to move.

stagebodyrequiredstring

Target stage name (case-insensitive).

reasonbodystring

Optional note for the activity log.

Request
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"
}'
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/stagesList pipeline stagesats:read

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.

Parameters

job_idquerystring

The job whose pipeline to read.

Request
curl "https://kumohr.com/api/v1/ats/pipeline/stages" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/searchFind candidates (AI search)ats:read

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.

Parameters

querybodystring

Who you are looking for, in plain words.

job_idbodystring

Rank the pool for this job (its criteria and stored fit scores).

required_skillsbodyarray

Skills every result must have (up to 3).

preferred_skillsbodyarray

Skills that rank people higher (up to 8).

locationsbodyarray

Cities, regions or countries.

min_yearsbodynumber

Minimum years of experience (people with no recorded experience are kept, ranked lower).

filtersbodyobject

Hard 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.

explainbodyboolean

Add a one-line reason and a profile quote per result (metered).

limitbodynumber

Results to return, 1-50 (default 20).

Request
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
}'
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
      }
    ]
  }
}
POST/api/v1/ats/assessmentsPost an assessment resultats:assessments:write

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.

Parameters

application_idbodyrequiredstring

The application (from the webhook payload).

providerbodyrequiredstring

Your product key, e.g. "genia". Stable across calls.

external_idbodyrequiredstring

Your id for this session or test.

statusbodyrequiredstring

Where the assessment is. Values: pending, invited, in_progress, completed, expired, declined, cancelled, error.

scorebodynumber

Overall score, 0–100.

passedbodyboolean

Your recommendation. Omit to let the route’s minimum score decide.

summarybodystring

Conclusion shown to recruiters.

strengthsbodystring

Strengths.

weaknessesbodystring

Weaknesses / concerns.

report_urlbodystring

https link to the full report or recording.

rawbodyobject

Your full result as JSON (up to 256 KB), kept for recruiters.

recommendationbodystring

Your recommendation for the recruiter. Values: advance, hold, reject.

confidencebodynumber

How sure you are, 0–1.

modebodystring

How the interview ran. Values: voice, text.

disclosurebodyobject

What 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_humanbodyboolean

With status "declined": the candidate asked to speak with a person. Never scored; the hiring team is told.

sentimentbodyobject

Your 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.

kindbodystring

ai_interview (default), skills_test, async_video, reference_check or custom.

dimension_scoresbodyarray

Per-dimension scores shown as bars: [{key, label, score 0-100, weight?, evidence: [{quote, t_ms?}]}] (up to 20).

transcriptbodyarray

The interview transcript: [{role: interviewer|candidate, text, t_ms?}] (up to 400 turns). Evidence timestamps jump to it.

Request
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"
}'
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/assessmentsList assessment resultsats:read

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

Parameters

application_idquerystring

Results for this application.

candidate_idquerystring

Results for this candidate.

Request
curl "https://kumohr.com/api/v1/ats/assessments" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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-routesList assessment routesats:read

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

Request
curl "https://kumohr.com/api/v1/ats/assessment-routes" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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-routesSet an assessment routeats:write

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.

Parameters

trigger_stagebodyrequiredstring

The stage the assessment runs in, e.g. "AI Interview".

pass_stagebodyrequiredstring

Where a pass goes.

fail_stagebodyrequiredstring

Where a fail goes.

min_scorebodynumber

Pass mark (0–100) used when a result has a score but no `passed`.

job_idbodystring

Only for this job.

providerbodystring

Only for results from this provider.

fail_policybodystring

On 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.

Request
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"
}'
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/resumeGet a candidate’s CVats:read

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.

Parameters

candidate_idqueryrequiredstring

The candidate.

Request
curl "https://kumohr.com/api/v1/ats/candidates/resume" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/detailGet one assessmentats:read

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).

Parameters

idqueryrequiredstring

The assessment id.

includequerystring

Comma-separated extras: transcript.

Request
curl "https://kumohr.com/api/v1/ats/assessments/detail" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/decisionRecord a recruiter decisionats:write

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.

Parameters

idbodyrequiredstring

The assessment id.

decisionbodyrequiredstring

The decision. Values: advance, hold, reject.

reasonbodystring

Why (required when overriding the recommendation).

Request
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"
}'
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/criteriaGet a job’s hiring criteriaats:read

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.

Parameters

job_idqueryrequiredstring

The job.

Request
curl "https://kumohr.com/api/v1/ats/jobs/criteria" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/criteriaSet a job’s hiring criteriaats:write

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.

Parameters

job_idbodyrequiredstring

The job.

criteriabodyrequiredarray

List of {label, category?, importance?, weight?, knockout?, min_years?, evidence_hint?, lint_justification?}.

match_thresholdbodynumber

Job-fit score (0–100) for talent-pool placement. Null uses the workspace default.

Request
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
}'
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-poolMatch the talent pool against a jobats:write

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.

Parameters

job_idbodyrequiredstring

The job.

Request
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"
}'
Response
{
  "data": {
    "run_id": "uuid",
    "pool_size": 142,
    "already_scored": 38,
    "estimated_credits": 208
  }
}
GET/api/v1/ats/jobs/match-poolGet talent-pool match resultsats:read

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.

Parameters

job_idqueryrequiredstring

The job.

Request
curl "https://kumohr.com/api/v1/ats/jobs/match-pool" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/placeAdd matched candidates to the pipelineats:write

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.

Parameters

job_idbodyrequiredstring

The job.

candidate_idsbodyrequiredarray

Candidates to place (up to 500).

Request
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"
}'
Response
{
  "data": {
    "placed": 12,
    "already_applied": 1
  }
}
POST/api/v1/ats/jobs/draftDraft a job with Kumo AIats:jobs:write

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.

Parameters

briefbodyrequiredstring

What you are hiring for, in your own words (any language).

answersbodyobject

Answers to earlier follow-ups: {title, seniority, where, salary}.

Request
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": {}
}'
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/jobsCreate a jobats:jobs:write

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.

Parameters

draftbodyobject

A draft from draft_job_with_ai (edited as you like).

titlebodystring

Without a draft: the job title.

departmentbodystring

Without a draft: the department.

locationbodystring

City or office (required unless remote).

work_arrangementbodystring

Onsite, Hybrid or Remote. Values: Onsite, Hybrid, Remote.

descriptionbodystring

The job ad (HTML or text).

stagesbodyarray

Hiring stages: [{name, objective?, assessment?: {type: none|form|ai_interview|template|partner, …}, pass_stage?, fail_policy?}].

requisition_codebodystring

Your own requisition number (e.g. from your HR system). Omit and Kumo numbers it (REQ-2026-0042). Unique per workspace.

publishbodyboolean

Publish immediately (subject to approval).

Request
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
}'
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/publishPublish a jobats:jobs:write

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.

Parameters

job_idbodyrequiredstring

The job.

Request
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"
}'
Response
{
  "data": {
    "job": {
      "id": "uuid",
      "status": "ACTIVE"
    },
    "already_live": false
  }
}
POST/api/v1/ats/jobs/stagesSet a job’s hiring stages and assessmentsats:jobs:write

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.

Parameters

job_idbodyrequiredstring

The job.

stagesbodyrequiredarray

[{id?, name, objective?, assessment?, pass_stage?, fail_stage?, fail_policy?}] in order.

Request
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"
}'
Response
{
  "data": {
    "stages": [
      {
        "id": "uuid",
        "name": "Screening"
      }
    ],
    "renamed": [],
    "deactivated": [],
    "assessments": [
      {
        "stage": "Screening",
        "templateId": "uuid",
        "automationId": "uuid"
      }
    ]
  }
}
GET/api/v1/ats/assessment-templatesList assessment templatesats:read

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

Parameters

job_idquerystring

Only this job’s templates (and the library).

kindquerystring

form or ai_interview. Values: form, ai_interview.

Request
curl "https://kumohr.com/api/v1/ats/assessment-templates" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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-templatesCreate or update an assessment templateats:assessments:manage

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).

Parameters

idbodystring

Update this template.

job_idbodystring

Attach to a job (omit for the workspace library).

kindbodystring

form or ai_interview. Values: form, ai_interview.

namebodyrequiredstring

Name shown to candidates.

configbodyrequiredobject

The questions or interview plan (see the example).

statusbodystring

draft or active. Values: draft, active.

Request
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"
}'
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/draftDraft an assessment with Kumo AIats:assessments:manage

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).

Parameters

kindbodyrequiredstring

form or ai_interview. Values: form, ai_interview.

job_idbodystring

The job to write it for.

stagebodystring

The stage it is for.

instructionsbodystring

What to focus on, in plain words.

currentbodyobject

An existing config to improve instead of starting fresh.

Request
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": {}
}'
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/sendSend a Kumo assessment to a candidateats:assessments:send

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).

Parameters

application_idbodyrequiredstring

The application.

template_idbodyrequiredstring

The assessment template.

send_emailbodyboolean

Kumo emails the link (default true).

expires_in_daysbodynumber

1-21 (default 7).

rotate_linkbodyboolean

Issue a new link for an open invite.

retakebodyboolean

Send again to someone who already completed it (default false).

Request
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
}'
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
  }
}

REST · Webhooks

4
GET/api/v1/webhooksList webhookswebhooks:manage

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

Request
curl "https://kumohr.com/api/v1/webhooks" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
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/webhooksSubscribe to eventswebhooks:manage

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.

Parameters

urlbodyrequiredstring

Your https endpoint.

eventsbodyrequiredstring

Event 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.

filtersbodyobject

Optional: {"stages": [stage names], "job_ids": [job ids]}.

labelbodystring

A name for this endpoint.

Request
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"
}'
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/webhooksRemove a webhookwebhooks:manage

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

Parameters

idqueryrequiredstring

The webhook id.

Request
curl -X DELETE "https://kumohr.com/api/v1/webhooks" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN" \
  -H "Idempotency-Key: unique-request-id"
Response
{
  "data": {
    "id": "uuid",
    "enabled": false
  }
}
POST/api/v1/webhooks/testSend a test eventwebhooks:manage

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.

Parameters

idbodyrequiredstring

The webhook id.

Request
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"
}'
Response
{
  "data": {
    "event_id": "evt_…",
    "delivered": true,
    "status": "delivered",
    "response_status": 200,
    "error": null
  }
}
Server-Sent Events

Live progress that reconnects.

Stream /api/v1/agent/runs/{id}/events. Kumo sends standard SSE ids so a dropped client can continue from its final durable event.

Connect
curl -N "https://kumohr.com/api/v1/agent/runs/RUN_ID/events?cursor=18201" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Events
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 status
agent.eventPlan, tool, action, artifact, warning, or outcome
heartbeatConnection liveness and current cursor
doneTerminal run; close the stream
errorTransport or data-source failure
Webhooks

Durable server-to-server outcomes.

SSE 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.published
Signature headers
Kumo-Webhook-Id: delivery_...
Kumo-Webhook-Timestamp: 1787290200
Kumo-Webhook-Signature: v1=7dc7...

# Signed input:
HMAC_SHA256(signing_secret, timestamp + "." + raw_body)
Verify with the SDK
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 });
Read the raw body once

JSON re-serialization changes bytes and invalidates the signature. Verify before parsing.

Model Context Protocol

Kumo speaks MCP both ways.

Inbound

Your agent calls Kumo

Point an MCP client at https://kumohr.com/api/mcp. Tools are filtered to the PAT's scopes and the named user's role.

  • Streamable HTTP and JSON-RPC 2.0
  • Protocol 2025-06-18 and 2025-03-26
  • Cursor, VS Code, Claude Code, and custom clients
Outbound

Kumo calls your system

Register a published HTTPS MCP server or run the Connector inside your network. Every external action remains human-confirmed.

  • Named actor, run, and action attribution
  • Stable idempotency keys and structured receipts
  • No inbound port with the Connector
MCP client config
{
  "mcpServers": {
    "kumo-hr": {
      "type": "http",
      "url": "https://kumohr.com/api/mcp",
      "headers": {
        "Authorization": "Bearer kumo_YOUR_TOKEN"
      }
    }
  }
}
Outbound Connector
KUMO_URL=https://kumohr.com \
KUMO_CONNECTOR_TOKEN=kumo_cn_... \
MCP_URL=http://127.0.0.1:8799/mcp \
npm run mcp:connector
Audit

Follow one request through everything.

Kumo correlates the credential, external session, message, model calls, plan, approvals, tools, MCP receipts, artifacts, credits, recovery, and final outcome.

Encrypted payloads

Full-fidelity business payloads use AES-256-GCM after secret redaction.

Hash-chained events

Tenant sequence, previous hash, event hash, and server signature expose tampering.

Human decisions

Plan approval, action decisions, amendments, and co-signatures are normalized records.

Signed export

Admins export a manifest and verify it through the Audit API.

Trace one run
curl "https://kumohr.com/api/v1/agent/audit?runId=RUN_ID&limit=100" \
  -H "Authorization: Bearer kumo_YOUR_TOKEN"
Errors and limits

Fail clearly and retry safely.

400Invalid request

Malformed fields, unsupported decision, or missing idempotency key.

401Unauthorized

Missing, unknown, revoked, expired, or invalid credential.

403Forbidden

Missing scope, live role denial, or admin-only operation.

404Not found

The resource does not exist or is not owned by this named user.

409Conflict

Invalid run state, duplicate in progress, or idempotency mismatch.

413File too large

The attachment exceeds the accepted upload limit.

415Unsupported media

Kumo cannot extract that attachment type.

429Rate limited

Wait for the number of seconds in Retry-After.

Error envelope
{
  "error": {
    "code": "idempotency_conflict",
    "message": "That Idempotency-Key was already used for a different request."
  },
  "requestId": "req_..."
}
Ready to integrate?

Bring your architecture.

We will map identities, system boundaries, named tools, approvals, and residency with your engineering team.

Book a technical sessionDownload OpenAPI
Kumo API Documentation · Agent API, REST, MCP and SDK