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:
- Created - Task is initialized by the AI system
- Pending - Task is waiting for human input
- In review - A human has picked the task up and is working on it, but has not submitted an outcome yet
- Completed - Human has provided the requested input
- Expired - Task has passed its expiration time without completion
- Canceled - The agent withdrew the task before a human answered it
Before You Start
To use the HITL API, you'll need a KirokuForms API key and should be familiar with MCP protocol basics. All requests require proper authentication headers.
API Endpoints
Request Human Review (Task Creation)
POST /api/mcp/tools/request-human-review This endpoint creates a new HITL task that will be presented to a human reviewer.
Request Body
{
"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
{
"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
{
"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.
{
"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.
Assigning an external email is a paid feature
Assigning a teammate who already has an account (through
settings.assignToUserId, or an assignToEmail
that matches an existing account) works on every plan. Assigning an address
with no account mints an external access link, and that is the paid feature:
a key on the FREE plan gets a 402 with the error code SUBSCRIPTION_REQUIRED, and no task is created.
{
"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.
{
"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 /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
{
"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)
{
"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
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 ofpending,in_review,completed,canceled, orexpired. 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
{
"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
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
{
"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.
{
"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
{
"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 Security
To verify webhook authenticity, the request includes a KirokuForms-Signature header. This header contains a timestamp and an HMAC signature of the
payload, using your webhook secret.
Always verify webhook signatures in production to ensure requests are legitimate and haven't been tampered with.
KirokuForms-Signature: t=1642260000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce308175258 Next Steps
Now that you understand the HITL API basics:
- Integrate with LangGraph for AI workflow orchestration
- Create a webhook handler for asynchronous workflows
- Explore form generation for creating dynamic forms
- Browse example implementations for common use cases