Skip to main content
Not every decision should be left to an AI. Feather’s Human-in-the-Loop (HITL) system lets you define exactly when a human needs to be in the conversation — whether that’s approving a high-stakes action before the agent executes it, or handing the entire thread to a live support agent. Both modes keep the customer experience coherent: the user sees consistent messages and the operator has full context from the start.

Two HITL modes

Approvals

The agent pauses mid-conversation before executing a tool call or workflow action. A human operator reviews the pending action, then approves or denies it. The agent automatically resumes with the decision and continues the conversation.

Handoffs

The agent transfers the entire conversation to a human operator. The operator sends messages that are relayed directly to the end user. When the issue is resolved, the operator closes the handoff and the session ends gracefully.

Handling approvals

When a conversation reaches an action that requires sign-off, the agent enters awaiting_approval status and waits — it will not proceed until a decision is recorded.
1

List pending approval requests

Poll this endpoint from your operator dashboard or webhook handler to surface requests that need attention.
curl "https://api-sandbox.featherhq.com/v1/hitl/approvals?status=pending" \
  -H "x-api-key: <your_api_key>"
The response lists approval summaries including the subject_ref, approver_role, and the suspend_message shown to the user.
2

Fetch full approval detail

Retrieve the complete record for a specific approval — including the action payload the agent wants to execute.
curl "https://api-sandbox.featherhq.com/v1/hitl/approvals/<approval_id>" \
  -H "x-api-key: <your_api_key>"
ApprovalRequestDetail fields:
FieldTypeDescription
idstringUnique approval request ID
conversation_idstringThe conversation this approval belongs to
subject_kindenumworkflow_node or tool_call
subject_refstringIdentifier of the node or tool awaiting approval
payloadobjectThe full action payload the agent intends to execute
approver_rolestringThe operator role required to approve this action
suspend_messagestringMessage currently displayed to the end user
timeout_atISO 8601When the request auto-expires if no decision is made
3

Submit your decision

Approve or deny the pending action. Include a note when denying so the agent can explain the outcome to the user.
curl -X POST \
  "https://api-sandbox.featherhq.com/v1/hitl/approvals/<approval_id>/decide" \
  -H "x-api-key: <your_api_key>" \
  -H "Content-Type: application/json" \
  -d '{"decision": "approve"}'
Once the decision is submitted, the conversation automatically resumes. If approved, the agent executes the action. If denied, the agent receives the denial note and responds to the user accordingly.

Handling handoffs

When an agent triggers a handoff, the conversation transitions to waiting_for_human status. The agent is no longer generating responses — a human operator takes over completely.
1

List pending handoffs

curl "https://api-sandbox.featherhq.com/v1/hitl/handoffs?status=requested" \
  -H "x-api-key: <your_api_key>"
2

Review the handoff context

Fetch the full handoff record to understand why the agent escalated and what the customer’s situation is before you write your first message.
curl "https://api-sandbox.featherhq.com/v1/hitl/handoffs/<handoff_id>" \
  -H "x-api-key: <your_api_key>"
The packet field contains everything the agent prepared for the handoff:
{
  "id": "hoff_01hxd4p6ns9qr7u1bgkm",
  "conversation_id": "conv_01hxd3m5mr8qp6t0zdfh",
  "status": "requested",
  "packet": {
    "static_text": "The customer is experiencing a billing discrepancy on their last invoice.",
    "reason": "Issue requires access to billing backend — outside agent permissions.",
    "summary": "Customer Alex reported a $50 overcharge on invoice #INV-2024-0891. They have already tried self-service and need manual correction.",
    "transcript_ptr": {
      "conversation_id": "conv_01hxd3m5mr8qp6t0zdfh",
      "up_to_seq": 12
    }
  }
}
Packet fieldDescription
static_textA short briefing sentence visible at the top of the handoff card.
reasonWhy the agent decided to escalate.
summaryA longer AI-generated summary of the conversation so far.
transcript_ptrWatermark pointer {conversation_id, up_to_seq} indicating the conversation message seq up to which the transcript had been captured at handoff time.
3

Relay a message to the user

Send messages as the human operator. They appear in the customer’s conversation thread immediately.
curl -X POST \
  "https://api-sandbox.featherhq.com/v1/hitl/handoffs/<handoff_id>/messages" \
  -H "x-api-key: <your_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hi, I'\''m Sarah from support. I can see the billing issue on your account — let me get that corrected for you right now."
  }'
You can send as many messages as needed. The customer replies are visible by polling GET /v1/v2/conversations/{id}/messages.
4

Resolve the handoff

When the issue is resolved, close the handoff. The conversation is marked HANDOFF_RESOLVED and the session ends.
curl -X POST \
  "https://api-sandbox.featherhq.com/v1/hitl/handoffs/<handoff_id>/resolve" \
  -H "x-api-key: <your_api_key>"
Poll GET /v1/v2/conversations/{id}/messages?since_seq={n} to receive the end user’s replies in real time during an active handoff. Pass the sequence number of the last message you’ve read as since_seq to receive only new messages.

Custom approval messages

While a conversation is paused waiting for approval, the end user sees a holding message. You can customize both the initial holding message and the follow-up patience message to match your brand voice.
curl -X PUT https://api-sandbox.featherhq.com/v1/hitl/approval-messages \
  -H "x-api-key: <your_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "holding_line": "Let me connect you with a specialist. One moment please.",
    "waiting_reply": "I'\''m still working on connecting you. Thank you for your patience."
  }'
FieldWhen it’s shown
holding_lineSent to the user immediately when the conversation enters awaiting_approval.
waiting_replySent if the user sends another message while still waiting (the “still here” acknowledgement).

Configuring HITL in workflows

Approval and handoff behaviors are configured as nodes in your agent’s workflow graph. Here’s how both node types look in a workflow definition:
{
  "id": "approve_refund",
  "type": "approval",
  "condition": {
    "type": "expression",
    "expression": "refund_amount > 100"
  },
  "approver_role": "billing_manager",
  "suspend_message": "I'm reviewing your refund request with our billing team. This usually takes just a moment.",
  "timeout_seconds": 300,
  "on_approve": "process_refund",
  "on_deny": "explain_denial"
}
An Approval Node’s condition is a deterministic expression: when it evaluates to false the node is a pass-through and no approval is requested. When it’s true, the runtime suspends and emits an approval request. On resume, on_approve and on_deny each name the next node to route to for that decision — a timeout is recorded as a deny, so it follows the on_deny path.