curl --request PUT \
--url https://api.erdo.ai/v1/lead-playbook \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"body": "<string>",
"agent_id": "<string>",
"daily_contact_cap": 123,
"enabled": true,
"handoff_alert_links": [
{
"label": "<string>",
"requires": [
"<string>"
],
"url": "<string>"
}
],
"handoff_alert_recipients": [
"<string>"
],
"signature_name": "<string>"
}
'import requests
url = "https://api.erdo.ai/v1/lead-playbook"
payload = {
"body": "<string>",
"agent_id": "<string>",
"daily_contact_cap": 123,
"enabled": True,
"handoff_alert_links": [
{
"label": "<string>",
"requires": ["<string>"],
"url": "<string>"
}
],
"handoff_alert_recipients": ["<string>"],
"signature_name": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
body: JSON.stringify('<string>'),
agent_id: '<string>',
daily_contact_cap: 123,
enabled: true,
handoff_alert_links: [{label: '<string>', requires: ['<string>'], url: '<string>'}],
handoff_alert_recipients: ['<string>'],
signature_name: '<string>'
})
};
fetch('https://api.erdo.ai/v1/lead-playbook', 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.erdo.ai/v1/lead-playbook",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'body' => '<string>',
'agent_id' => '<string>',
'daily_contact_cap' => 123,
'enabled' => true,
'handoff_alert_links' => [
[
'label' => '<string>',
'requires' => [
'<string>'
],
'url' => '<string>'
]
],
'handoff_alert_recipients' => [
'<string>'
],
'signature_name' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.erdo.ai/v1/lead-playbook"
payload := strings.NewReader("{\n \"body\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"daily_contact_cap\": 123,\n \"enabled\": true,\n \"handoff_alert_links\": [\n {\n \"label\": \"<string>\",\n \"requires\": [\n \"<string>\"\n ],\n \"url\": \"<string>\"\n }\n ],\n \"handoff_alert_recipients\": [\n \"<string>\"\n ],\n \"signature_name\": \"<string>\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.put("https://api.erdo.ai/v1/lead-playbook")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"body\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"daily_contact_cap\": 123,\n \"enabled\": true,\n \"handoff_alert_links\": [\n {\n \"label\": \"<string>\",\n \"requires\": [\n \"<string>\"\n ],\n \"url\": \"<string>\"\n }\n ],\n \"handoff_alert_recipients\": [\n \"<string>\"\n ],\n \"signature_name\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/lead-playbook")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"body\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"daily_contact_cap\": 123,\n \"enabled\": true,\n \"handoff_alert_links\": [\n {\n \"label\": \"<string>\",\n \"requires\": [\n \"<string>\"\n ],\n \"url\": \"<string>\"\n }\n ],\n \"handoff_alert_recipients\": [\n \"<string>\"\n ],\n \"signature_name\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"agent": {
"id": "<string>",
"name": "<string>",
"phone_number": "<string>",
"slug": "<string>"
},
"agent_id": "<string>",
"body": "<string>",
"daily_contact_cap": 123,
"enabled": true,
"enabled_at": "2023-11-07T05:31:56Z",
"exists": true,
"handoff_alert_links": [
{
"label": "<string>",
"requires": [
"<string>"
],
"url": "<string>"
}
],
"handoff_alert_recipients": [
"<string>"
],
"policy_digest": "<string>",
"read_back": {
"accompanying_email": {
"mode": "<string>",
"when": "<string>"
},
"actions": [
{
"kind": "<string>",
"mode": "<string>",
"when": "<string>"
}
],
"cadence": {
"confirmed": {
"between_channels": [
{
"after": "<string>",
"minutes": 123,
"next": "<string>"
}
],
"call_minutes": 123,
"email_minutes": 123,
"sms_minutes": 123,
"whatsapp_minutes": 123
},
"new_lead_grace_minutes": 123,
"unanswered_attempts": [
"<string>"
],
"unconfirmed": {
"between_channels": [
{
"after": "<string>",
"minutes": 123,
"next": "<string>"
}
],
"call_minutes": 123,
"email_minutes": 123,
"sms_minutes": 123,
"whatsapp_minutes": 123
}
},
"flow": {
"any_time": [
{
"action": "<string>",
"exit_to": "<string>",
"if": "<string>",
"source": "<string>",
"source_found": true
}
],
"conditions": [
{
"builtin": true,
"key": "<string>",
"label": "<string>",
"options": [
{
"label": "<string>",
"value": "<string>"
}
],
"source": "<string>",
"source_found": true
}
],
"conflicts": [
{
"description": "<string>",
"sources": [
"<string>"
]
}
],
"stages": [
{
"name": "<string>",
"phases": [
{
"aim": "<string>",
"name": "<string>",
"steps": [
{
"action": "<string>",
"detail": "<string>",
"exit_to": "<string>",
"label": "<string>",
"only_if": [
{
"key": "<string>",
"values": [
"<string>"
]
}
],
"source": "<string>",
"source_found": true,
"when": "<string>"
}
]
}
]
}
]
},
"limits": [
{
"kind": "<string>",
"mode": "<string>",
"reason": "<string>"
}
],
"priority": {
"forbidden_factors": [
"<string>"
],
"high": [
"<string>"
],
"low": [
"<string>"
],
"medium": [
"<string>"
]
},
"stages": [
{
"actions": [
"<string>"
],
"label": "<string>",
"name": "<string>",
"summary": "<string>",
"tone": "<string>"
}
],
"working_hours": {
"days": [
"<string>"
],
"end": "<string>",
"start": "<string>",
"timezone": "<string>"
}
},
"revision": 123,
"signature_name": "<string>",
"updated_at": "2023-11-07T05:31:56Z",
"updated_by": "<string>"
}{
"code": "not_found",
"details": {},
"message": "<string>"
}PutLeadPlaybookAPI saves the playbook text, reads it into the typed
read-back, and stores both. Organization admins only.
There is deliberately no MCP tool for this: enabling a playbook that marks email automatic lets Erdo email real leads without a person, and that is a person’s decision. A scoped API key is refused.
curl --request PUT \
--url https://api.erdo.ai/v1/lead-playbook \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"body": "<string>",
"agent_id": "<string>",
"daily_contact_cap": 123,
"enabled": true,
"handoff_alert_links": [
{
"label": "<string>",
"requires": [
"<string>"
],
"url": "<string>"
}
],
"handoff_alert_recipients": [
"<string>"
],
"signature_name": "<string>"
}
'import requests
url = "https://api.erdo.ai/v1/lead-playbook"
payload = {
"body": "<string>",
"agent_id": "<string>",
"daily_contact_cap": 123,
"enabled": True,
"handoff_alert_links": [
{
"label": "<string>",
"requires": ["<string>"],
"url": "<string>"
}
],
"handoff_alert_recipients": ["<string>"],
"signature_name": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
body: JSON.stringify('<string>'),
agent_id: '<string>',
daily_contact_cap: 123,
enabled: true,
handoff_alert_links: [{label: '<string>', requires: ['<string>'], url: '<string>'}],
handoff_alert_recipients: ['<string>'],
signature_name: '<string>'
})
};
fetch('https://api.erdo.ai/v1/lead-playbook', 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.erdo.ai/v1/lead-playbook",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'body' => '<string>',
'agent_id' => '<string>',
'daily_contact_cap' => 123,
'enabled' => true,
'handoff_alert_links' => [
[
'label' => '<string>',
'requires' => [
'<string>'
],
'url' => '<string>'
]
],
'handoff_alert_recipients' => [
'<string>'
],
'signature_name' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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.erdo.ai/v1/lead-playbook"
payload := strings.NewReader("{\n \"body\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"daily_contact_cap\": 123,\n \"enabled\": true,\n \"handoff_alert_links\": [\n {\n \"label\": \"<string>\",\n \"requires\": [\n \"<string>\"\n ],\n \"url\": \"<string>\"\n }\n ],\n \"handoff_alert_recipients\": [\n \"<string>\"\n ],\n \"signature_name\": \"<string>\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.put("https://api.erdo.ai/v1/lead-playbook")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"body\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"daily_contact_cap\": 123,\n \"enabled\": true,\n \"handoff_alert_links\": [\n {\n \"label\": \"<string>\",\n \"requires\": [\n \"<string>\"\n ],\n \"url\": \"<string>\"\n }\n ],\n \"handoff_alert_recipients\": [\n \"<string>\"\n ],\n \"signature_name\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/lead-playbook")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"body\": \"<string>\",\n \"agent_id\": \"<string>\",\n \"daily_contact_cap\": 123,\n \"enabled\": true,\n \"handoff_alert_links\": [\n {\n \"label\": \"<string>\",\n \"requires\": [\n \"<string>\"\n ],\n \"url\": \"<string>\"\n }\n ],\n \"handoff_alert_recipients\": [\n \"<string>\"\n ],\n \"signature_name\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"agent": {
"id": "<string>",
"name": "<string>",
"phone_number": "<string>",
"slug": "<string>"
},
"agent_id": "<string>",
"body": "<string>",
"daily_contact_cap": 123,
"enabled": true,
"enabled_at": "2023-11-07T05:31:56Z",
"exists": true,
"handoff_alert_links": [
{
"label": "<string>",
"requires": [
"<string>"
],
"url": "<string>"
}
],
"handoff_alert_recipients": [
"<string>"
],
"policy_digest": "<string>",
"read_back": {
"accompanying_email": {
"mode": "<string>",
"when": "<string>"
},
"actions": [
{
"kind": "<string>",
"mode": "<string>",
"when": "<string>"
}
],
"cadence": {
"confirmed": {
"between_channels": [
{
"after": "<string>",
"minutes": 123,
"next": "<string>"
}
],
"call_minutes": 123,
"email_minutes": 123,
"sms_minutes": 123,
"whatsapp_minutes": 123
},
"new_lead_grace_minutes": 123,
"unanswered_attempts": [
"<string>"
],
"unconfirmed": {
"between_channels": [
{
"after": "<string>",
"minutes": 123,
"next": "<string>"
}
],
"call_minutes": 123,
"email_minutes": 123,
"sms_minutes": 123,
"whatsapp_minutes": 123
}
},
"flow": {
"any_time": [
{
"action": "<string>",
"exit_to": "<string>",
"if": "<string>",
"source": "<string>",
"source_found": true
}
],
"conditions": [
{
"builtin": true,
"key": "<string>",
"label": "<string>",
"options": [
{
"label": "<string>",
"value": "<string>"
}
],
"source": "<string>",
"source_found": true
}
],
"conflicts": [
{
"description": "<string>",
"sources": [
"<string>"
]
}
],
"stages": [
{
"name": "<string>",
"phases": [
{
"aim": "<string>",
"name": "<string>",
"steps": [
{
"action": "<string>",
"detail": "<string>",
"exit_to": "<string>",
"label": "<string>",
"only_if": [
{
"key": "<string>",
"values": [
"<string>"
]
}
],
"source": "<string>",
"source_found": true,
"when": "<string>"
}
]
}
]
}
]
},
"limits": [
{
"kind": "<string>",
"mode": "<string>",
"reason": "<string>"
}
],
"priority": {
"forbidden_factors": [
"<string>"
],
"high": [
"<string>"
],
"low": [
"<string>"
],
"medium": [
"<string>"
]
},
"stages": [
{
"actions": [
"<string>"
],
"label": "<string>",
"name": "<string>",
"summary": "<string>",
"tone": "<string>"
}
],
"working_hours": {
"days": [
"<string>"
],
"end": "<string>",
"start": "<string>",
"timezone": "<string>"
}
},
"revision": 123,
"signature_name": "<string>",
"updated_at": "2023-11-07T05:31:56Z",
"updated_by": "<string>"
}{
"code": "not_found",
"details": {},
"message": "<string>"
}Authorizations
An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).
Body
Response
Success response
Show child attributes
Show child attributes
the same organization. Null when none is linked.
It counts every contact the engine makes or puts in front of a person: emails, texts, WhatsApp messages and calls, each one place (an email sent beside a gated text or call is a second). Handoffs, waits and closes never count, and nor does a concierge answering a lead inside a WhatsApp, SMS, chat or phone conversation. 200 when never set.
before it is left alone until somebody starts the engine on it; absent for a playbook that was never on.
then carries the default template and ReadBack is empty.
caller declared them: URL templates filled from the lead (backend/utils/linktemplate). Empty means the alert carries no button. Erdo has no page of its own for a lead's follow-up, so it never invents a destination; the application the organization works its leads in declares where "open this lead" goes. Like the recipients, it is a delivery detail read live at send time and never frozen on a revision. Never nil.
Show child attributes
Show child attributes
handoff — a decision it cannot carry out, which needs a person at the sales desk. Empty means the alert is off: there is no default audience and no fallback to the organization's members, so a development that has not filled this in is told nothing and no email is sent.
It is deliberately not on a playbook REVISION, unlike SignatureName: it is who gets told, not what a decision says, so it is read live at send time and changing it takes effect at once rather than at the next save. See migration 012.
a decision that is genuinely out of date from one whose playbook has merely been re-saved. A decision whose policy_digest equals this one was decided under the playbook as it stands; one that differs is due to be decided again, and the sweep will. See policy.go.
Only Actions[].Mode and WorkingHours are enforced; the stages and the priority ladder are a picture of the text drawn for the person who wrote it, and every evaluation still reads the whole text.
Show child attributes
Show child attributes
signed and sent as, and what a call brief opens with. Empty falls back to the linked concierge's display name, which is what every playbook written before this setting existed does. See writeIdentitySection.

