KirokuForms

Human-in-the-Loop API

KirokuForms provides a powerful Human-in-the-Loop (HITL) API that allows AI systems to request human input through dynamically generated forms when needed.

What is Human-in-the-Loop?

Human-in-the-Loop is a collaborative approach where AI systems can request human judgment for tasks that require:

  • Verification of AI-generated content
  • Decision-making in high-stakes situations
  • Additional information not available to the AI system
  • Judgment on ambiguous or subjective matters
  • Feedback to improve AI performance

KirokuForms implements HITL capabilities through the Model Context Protocol (MCP), which provides a standardized way for AI systems to request human input, wait for responses, and incorporate that feedback back into their workflows.

Key Concepts

HITL Tasks

A HITL task represents a request for human input. Each task includes:

  • Title - Brief description of what is being requested
  • Description - Detailed instructions for the human reviewer
  • Form - The interface used to collect the human input
  • Initial Data - Contextual information or pre-filled values
  • Expiration - Optional time limit for completing the task

Form Generation Methods

Forms for HITL tasks can be created in several ways:

  • Custom Field Definitions - Define each form field manually
  • Template-Based - Use a pre-defined form template
  • Data-Based - Dynamically generate fields based on data structure

What a task costs against your plan

When you pass fields, we build a review page for that one task and hand back its formId. It does not count towards the forms in your plan, and it does not appear in your forms list, so an agent can open as many cases as your plan allows without eating into the forms you built by hand.

What human review costs is the same thing an ordinary form costs: a completed review is a submission, and it counts against your monthly submission allowance on the pricing page. Nothing else is metered.

There are two safety rails, and they exist to stop an agent stuck in a loop rather than to separate the plans: reviews created per day, and reviews left open at once. They are set far above any real queue, so you should never meet one. If you do, the API answers 429 with RATE_LIMIT_EXCEEDED or CONCURRENT_TASK_LIMIT_EXCEEDED. The daily one clears on a rolling 24 hours; completing or cancelling a case frees an open slot. Getting either is a sign something is looping, so it is worth looking rather than retrying.

Passing templateId instead reuses a form you already own. That form is one of yours and counts normally, however many tasks point at it.

Task Lifecycle

HITL tasks follow this lifecycle:

  1. Created - Task is initialized by the AI system
  2. Pending - Task is waiting for human input
  3. In review - A human has picked the task up and is working on it, but has not submitted an outcome yet
  4. Completed - Human has provided the requested input
  5. Expired - Task has passed its expiration time without completion
  6. Canceled - The agent withdrew the task before a human answered it

API Endpoints

Request Human Review (Task Creation)

Create HITL Task Endpoint
POST /api/mcp/tools/request-human-review

This endpoint creates a new HITL task that will be presented to a human reviewer.

Request Body

Request Human Review - Custom Fields
{
  "title": "Verify Customer Information",
  "description": "Please review this customer data for accuracy",
  "initialData": {
    "customer_name": "Acme Corp",
    "revenue": 1500000,
    "industry": "Technology"
  },
  "fields": [
    {
      "type": "text",
      "label": "Company Name",
      "name": "customer_name",
      "required": true,
      "defaultValue": "Acme Corp"
    },
    {
      "type": "number",
      "label": "Annual Revenue",
      "name": "revenue",
      "required": true
    },
    {
      "type": "radio",
      "label": "Information Verified?",
      "name": "is_verified",
      "required": true,
      "options": [
        {"label": "Yes, information is correct", "value": "yes"},
        {"label": "No, needs correction", "value": "no"}
      ]
    },
    {
      "type": "textarea",
      "label": "Notes",
      "name": "notes",
      "required": false,
      "placeholder": "Additional comments..."
    }
  ],
  "settings": {
    "expiration": "2h",
    "priority": "high",
    "callbackUrl": "https://your-server.com/webhooks/hitl"
  }
}

Alternative: Using a Template

Request Human Review - Using Template
{
  "title": "Verify Customer Information",
  "description": "Please review this customer data for accuracy",
  "templateId": "customer-verification-template",
  "initialData": {
    "customer_name": "Acme Corp",
    "revenue": 1500000,
    "industry": "Technology"
  },
  "settings": {
    "expiration": "2h",
    "priority": "high",
    "callbackUrl": "https://your-server.com/webhooks/hitl"
  }
}

Response

Task Creation Response
{
  "success": true,
  "data": {
    "taskId": "task-123456",
    "hitlTaskId": "hitl-abc123",
    "formId": "form-xyz789",
    "formUrl": "https://www.kirokuforms.com/hitl/task/task-123456?token=abc...xyz"
  },
  "message": "Human review task created successfully"
}

Assigning a Task to a Person

By default a HITL task is not routed to anyone in particular: it is visible to the account that created it. To put a specific person on the case, add settings.assignToEmail (with an optional settings.assigneeName) when you create the task.

Assign at creation by email
{
  "title": "Approve the Q3 vendor invoice",
  "description": "Please review and approve this invoice before we pay it.",
  "templateId": "invoice-approval-template",
  "initialData": {
    "vendor": "Acme Supplies",
    "amount": 4820.50,
    "due_date": "2025-02-01"
  },
  "settings": {
    "assignToEmail": "jane@acme.com",
    "assigneeName": "Jane Okoye",
    "priority": "high",
    "expiration": "2d"
  }
}

The assignee needs no KirokuForms account. They receive their own tokenized link out of band (by email, or a Slack DM, covered below) and open the Approve / Reject / Edit surface without signing up. The success response is the same task-creation body shown above; the assignee's access token is a credential for acting on the task, so it is never returned in the API response.

402 Payment Required (FREE key assigning an external email)
{
  "success": false,
  "message": "Assigning a case to an email address with no KirokuForms account is a paid feature. Assigning a teammate who has an account is not.",
  "error": {
    "code": "SUBSCRIPTION_REQUIRED",
    "message": "Assigning a case to an email address with no KirokuForms account is a paid feature. Assigning a teammate who has an account is not."
  }
}

Delivering through Slack instead of email

When the account has Slack connected with the direct-assign scopes and the assignee's email resolves to a member of that workspace, the assignment is delivered as a Slack DM carrying Approve / Reject / Open buttons and no email is sent. If the person is not in the workspace, Slack is not connected, or Slack returns an error, delivery falls back to email. This is the default, when settings.assignToSlack is unset or true.

Set settings.assignToSlack to false to email a teammate even when they are reachable in Slack. It changes only the delivery channel: the external-email paywall is the one gate, and this setting cannot turn on a channel that is not connected. Slack reaches your workspace; email reaches anyone.

Force email delivery for a teammate
{
  "title": "Approve the Q3 vendor invoice",
  "templateId": "invoice-approval-template",
  "settings": {
    "assignToEmail": "jane@acme.com",
    "assignToSlack": false
  }
}

A tokenized link stays in a DM

The Approve / Reject buttons and the tokenized Open link are sent in a direct message, never to a shared channel. The token is a bearer credential: whoever holds it can act as the assignee without logging in, so it goes to that one person only, the same trust model as the email link.

Get Task Status

Get Task Status Endpoint
GET /api/mcp/resources/hitl/tasks/{taskId}

This endpoint retrieves the current status and details of a HITL task. It requires an API key with the hitl:read scope, sent as Authorization: Bearer <key>, and returns only tasks the key's owner created. A task belonging to another account returns 404. The reviewer's access token is a credential for submitting the form, so it is not part of this response.

Response

Task Status Response
{
  "success": true,
  "data": {
    "id": "hitl-abc123",
    "taskId": "task-123456",
    "title": "Verify Customer Information",
    "description": "Please review this customer data for accuracy",
    "status": "pending",
    "createdAt": "2025-01-15T14:30:00Z",
    "updatedAt": "2025-01-15T14:30:00Z",
    "expiresAt": "2025-01-15T16:30:00Z",
    "formId": "form-xyz789",
    "template": {
      "id": "customer-verification-template",
      "name": "Customer Verification",
      "fields": [
        // Field definitions...
      ]
    },
    "initialData": {
      "customer_name": "Acme Corp",
      "revenue": 1500000,
      "industry": "Technology"
    },
    "submission": null
  }
}

There is no separate result endpoint. When the task reaches completed, this same response carries the reviewer's answers in a submission object, where submission.data maps each field name to its value, so polling this endpoint is how an agent reads the outcome.

Response (Completed Task)

Completed Task Status Response
{
  "success": true,
  "data": {
    "id": "hitl-abc123",
    "taskId": "task-123456",
    "title": "Verify Customer Information",
    "status": "completed",
    "createdAt": "2025-01-15T14:30:00Z",
    "completedAt": "2025-01-15T15:12:00Z",
    "expiresAt": "2025-01-15T16:30:00Z",
    "submission": {
      "id": "sub-789",
      "createdAt": "2025-01-15T15:12:00Z",
      "data": {
        "customer_name": "Acme Corporation",
        "revenue": 1750000,
        "is_verified": "yes"
      }
    }
  }
}

Task Status Values

Every task's status is one of five values. Three are terminal: completed, canceled, and expired never change again. The other two, pending and in_review, are not terminal, so a task in either state can still move. A client polling for the outcome should stop once the task reaches a terminal status, since polling a terminal task returns the same answer every time.

Status Terminal? Meaning
pending No The task is waiting for a human. A new task is created in this state.
in_review No A human has opened the task and is working on it, but has not submitted an outcome yet.
completed Yes A human submitted a response, or approved or rejected the request. The answer is in the submission object on the task.
canceled Yes The agent withdrew the task before a human answered it.
expired Yes The task passed its expiresAt deadline without completion.

A task does not expire the instant its expiresAt passes. A scheduled job runs about once an hour and moves any pending or in_review task past its expiresAt to expired. So a client polling a task it created sees the task settle even when no human ever opens it, and never has to expire the task itself.

List Tasks

List Tasks Endpoint
GET /api/mcp/resources/hitl/tasks

This endpoint lists the tasks the key's owner created, newest first. It requires an API key with the hitl:read scope, sent as Authorization: Bearer <key>. Three optional query parameters filter and page the result:

  • status: return only tasks in this state, one of pending, in_review, completed, canceled, or expired. Omit it to return every state.
  • limit: how many tasks to return, 1 to 100 (default 10).
  • offset: how many tasks to skip first, 0 or more (default 0).

Response

List Tasks Response
{
  "success": true,
  "data": {
    "tasks": [
      {
        "id": "hitl-abc123",
        "taskId": "task-123456",
        "title": "Verify Customer Information",
        "status": "pending",
        "priority": "medium",
        "createdAt": "2025-01-15T14:30:00Z",
        "expiresAt": "2025-01-15T16:30:00Z",
        "completedAt": null,
        "template": {
          "id": "customer-verification-template",
          "name": "Verify Customer Information",
          "slug": "customer-verification"
        }
      }
    ],
    "total": 1,
    "limit": 10,
    "offset": 0
  }
}

Cancel Task

Cancel Task Endpoint
POST /api/mcp/resources/hitl/tasks/{taskId}/cancel

This endpoint cancels a pending HITL task and revokes its reviewer links, so a human who already has the URL can no longer submit it. It requires an API key with the hitl:create scope, sent as Authorization: Bearer <key>, and cancels only tasks the key's owner created. A task belonging to another account returns 404. No request body is needed.

Response

Cancel Task Response
{
  "success": true,
  "data": {
    "id": "clx9f0k2b0001abcd1234",
    "taskId": "task-123456",
    "title": "Verify customer information",
    "status": "canceled"
  },
  "message": "Task canceled successfully"
}

Response (Task Already Settled)

Only a pending task can be canceled. A task that is already completed, canceled, or expired returns 409 and is left untouched, so cancelling a task a human has already answered cannot discard their submission.

Cancel Conflict Response
{
  "success": false,
  "error": {
    "code": "TASK_NOT_PENDING",
    "message": "Only a pending task can be canceled; this task is completed."
  }
}

Field Types

The HITL API supports these field types for form generation:

Type Description Properties
text Single-line text input label, name, required, defaultValue, placeholder, validation
textarea Multi-line text input label, name, required, defaultValue, placeholder, rows, validation
number Numeric input label, name, required, defaultValue, min, max, step, validation
email Email input with validation label, name, required, defaultValue, placeholder, validation
select Dropdown selection label, name, required, defaultValue, options, validation
radio Radio button group label, name, required, defaultValue, options, validation
checkbox Checkbox or checkbox group label, name, required, defaultValue, options, validation
date Date picker label, name, required, defaultValue, min, max, validation

Webhook Integration

Webhooks allow your application to receive real-time notifications when HITL tasks are completed. To use webhooks, provide a callbackUrl in the task settings.

Webhook Payload

Webhook Payload Example
{
  "eventType": "hitl.task.completed",
  "taskId": "task-123456",
  "timestamp": "2025-01-15T15:30:00Z",
  "data": {
    "status": "completed",
    "formData": {
      // The submitted form data
      "customer_name": "Acme Corporation",
      "revenue": 1750000,
      "industry": "Technology",
      "is_verified": "yes",
      "notes": "Updated company name and revenue based on latest filing."
    }
  }
}
Webhook Signature Header
KirokuForms-Signature: t=1642260000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce308175258

Next Steps

Now that you understand the HITL API basics:

  1. Integrate with LangGraph for AI workflow orchestration
  2. Create a webhook handler for asynchronous workflows
  3. Explore form generation for creating dynamic forms
  4. Browse example implementations for common use cases