Hand Off Conversation to a Human
curl --request POST \
--url https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff \
--header 'Content-Type: application/json' \
--header 'authorization: <authorization>' \
--data '
{
"reason": "<string>",
"metadata": {},
"actor": "<string>",
"webhook": "<string>",
"notify_number": "<string>"
}
'import requests
url = "https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff"
payload = {
"reason": "<string>",
"metadata": {},
"actor": "<string>",
"webhook": "<string>",
"notify_number": "<string>"
}
headers = {
"authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({
reason: '<string>',
metadata: {},
actor: '<string>',
webhook: '<string>',
notify_number: '<string>'
})
};
fetch('https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reason' => '<string>',
'metadata' => [
],
'actor' => '<string>',
'webhook' => '<string>',
'notify_number' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"authorization: <authorization>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff"
payload := strings.NewReader("{\n \"reason\": \"<string>\",\n \"metadata\": {},\n \"actor\": \"<string>\",\n \"webhook\": \"<string>\",\n \"notify_number\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff")
.header("authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"reason\": \"<string>\",\n \"metadata\": {},\n \"actor\": \"<string>\",\n \"webhook\": \"<string>\",\n \"notify_number\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reason\": \"<string>\",\n \"metadata\": {},\n \"actor\": \"<string>\",\n \"webhook\": \"<string>\",\n \"notify_number\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"message": "Conversation handed off to a human",
"already_active": false,
"conversation": {
"id": "8f1c2a4e-1b7d-4f2a-9c1e-2d3f4a5b6c7d",
"is_active": true,
"current_node_id": "transfer-to-human",
"human_handoff_at": "2026-09-02T18:04:11.000Z",
"human_handoff_reason": "Final rate negotiation",
"human_handoff_metadata": { "source": "api", "actor": "broker@example.com", "load_id": "L-77" },
"updated_at": "2026-09-02T18:04:11.000Z"
}
},
"errors": null
}
Messaging
Hand Off Conversation to a Human
Pause the agent on a conversation so a human can take over the thread.
POST
/
v1
/
sms
/
conversations
/
{conversation_id}
/
handoff
Hand Off Conversation to a Human
curl --request POST \
--url https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff \
--header 'Content-Type: application/json' \
--header 'authorization: <authorization>' \
--data '
{
"reason": "<string>",
"metadata": {},
"actor": "<string>",
"webhook": "<string>",
"notify_number": "<string>"
}
'import requests
url = "https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff"
payload = {
"reason": "<string>",
"metadata": {},
"actor": "<string>",
"webhook": "<string>",
"notify_number": "<string>"
}
headers = {
"authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({
reason: '<string>',
metadata: {},
actor: '<string>',
webhook: '<string>',
notify_number: '<string>'
})
};
fetch('https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'reason' => '<string>',
'metadata' => [
],
'actor' => '<string>',
'webhook' => '<string>',
'notify_number' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"authorization: <authorization>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff"
payload := strings.NewReader("{\n \"reason\": \"<string>\",\n \"metadata\": {},\n \"actor\": \"<string>\",\n \"webhook\": \"<string>\",\n \"notify_number\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff")
.header("authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"reason\": \"<string>\",\n \"metadata\": {},\n \"actor\": \"<string>\",\n \"webhook\": \"<string>\",\n \"notify_number\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.bland.ai/v1/sms/conversations/{conversation_id}/handoff")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"reason\": \"<string>\",\n \"metadata\": {},\n \"actor\": \"<string>\",\n \"webhook\": \"<string>\",\n \"notify_number\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"message": "Conversation handed off to a human",
"already_active": false,
"conversation": {
"id": "8f1c2a4e-1b7d-4f2a-9c1e-2d3f4a5b6c7d",
"is_active": true,
"current_node_id": "transfer-to-human",
"human_handoff_at": "2026-09-02T18:04:11.000Z",
"human_handoff_reason": "Final rate negotiation",
"human_handoff_metadata": { "source": "api", "actor": "broker@example.com", "load_id": "L-77" },
"updated_at": "2026-09-02T18:04:11.000Z"
}
},
"errors": null
}
Enterprise Feature - SMS is only available on Enterprise plans.
- The agent stops replying on this conversation. Inbound messages are still stored, and each one still fires the message webhook with
sender: "USER", so your system sees every reply in real time. - The inactivity timeout is suspended.
- Your human replies on the same thread, from the same number, with Send Message on Conversation.
- A
status: "human_handoff"webhook fires once at the transition.
is_active: false, which makes the next inbound message start a new conversation.
You can also trigger a handoff from inside a pathway with the Transfer to Human node.
Headers
string
required
Your API key for authentication.
Path Parameters
string
required
The conversation to hand off.
Body Parameters
string
Why the thread was handed off. Returned as
reason on the human_handoff status webhook and shown in the dashboard.object
Free-form data about the handoff (rep id, ticket, lane, and so on). Merged into
metadata.human_handoff on every webhook for this conversation while the handoff is active.string
Who took the thread, for the audit trail (an email, a rep id).
string
Replace the conversation’s webhook URL at handoff time, for example to route relayed messages to your live-agent service.
string
E.164 number to text a heads-up to, from the agent number, with the conversation ID and reason.
Response
boolean
true when the conversation was already handed off. The call is idempotent.object
id, is_active, current_node_id, human_handoff_at, human_handoff_reason, human_handoff_metadata, updated_at.{
"data": {
"message": "Conversation handed off to a human",
"already_active": false,
"conversation": {
"id": "8f1c2a4e-1b7d-4f2a-9c1e-2d3f4a5b6c7d",
"is_active": true,
"current_node_id": "transfer-to-human",
"human_handoff_at": "2026-09-02T18:04:11.000Z",
"human_handoff_reason": "Final rate negotiation",
"human_handoff_metadata": { "source": "api", "actor": "broker@example.com", "load_id": "L-77" },
"updated_at": "2026-09-02T18:04:11.000Z"
}
},
"errors": null
}
Was this page helpful?