curl --request POST \
--url https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"kind": "<string>",
"decided_by_external_ref": "<string>",
"note": "<string>",
"until": "2023-11-07T05:31:56Z"
}
'import requests
url = "https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions"
payload = {
"kind": "<string>",
"decided_by_external_ref": "<string>",
"note": "<string>",
"until": "2023-11-07T05:31:56Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
kind: '<string>',
decided_by_external_ref: '<string>',
note: '<string>',
until: '2023-11-07T05:31:56Z'
})
};
fetch('https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions', 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/datasets/{datasetSlug}/leads/{leadRef}/next-actions",
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([
'kind' => '<string>',
'decided_by_external_ref' => '<string>',
'note' => '<string>',
'until' => '2023-11-07T05:31:56Z'
]),
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/datasets/{datasetSlug}/leads/{leadRef}/next-actions"
payload := strings.NewReader("{\n \"kind\": \"<string>\",\n \"decided_by_external_ref\": \"<string>\",\n \"note\": \"<string>\",\n \"until\": \"2023-11-07T05:31:56Z\"\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"kind\": \"<string>\",\n \"decided_by_external_ref\": \"<string>\",\n \"note\": \"<string>\",\n \"until\": \"2023-11-07T05:31:56Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"kind\": \"<string>\",\n \"decided_by_external_ref\": \"<string>\",\n \"note\": \"<string>\",\n \"until\": \"2023-11-07T05:31:56Z\"\n}"
response = http.request(request)
puts response.read_body{
"accompanying_email": {
"body_markdown": "<string>",
"error": "<string>",
"execution_ref": "<string>",
"sent_at": "2023-11-07T05:31:56Z",
"status": "<string>",
"subject": "<string>",
"to": "<string>"
},
"action_input": {},
"action_kind": "<string>",
"approval_request_id": "<string>",
"canonical_lead_id": "<string>",
"created_by": "<string>",
"dataset_id": "<string>",
"decided_at": "2023-11-07T05:31:56Z",
"decided_by": "<string>",
"decided_by_external_ref": "<string>",
"due_at": "2023-11-07T05:31:56Z",
"error": "<string>",
"evaluated_at": "2023-11-07T05:31:56Z",
"executed_at": "2023-11-07T05:31:56Z",
"execution_ref": "<string>",
"facts": {
"bedrooms": {
"source_column": "<string>",
"value": "<string>"
},
"broker": {
"source_column": "<string>",
"value": "<string>"
},
"budget": {
"source_column": "<string>",
"value": "<string>"
},
"financing": {
"source_column": "<string>",
"value": "<string>"
},
"language": {
"source_column": "<string>",
"value": "<string>"
},
"purpose": {
"source_column": "<string>",
"value": "<string>"
},
"timeline": {
"source_column": "<string>",
"value": "<string>"
}
},
"handoff_alert": {
"error": "<string>",
"execution_ref": "<string>",
"sent_at": "2023-11-07T05:31:56Z",
"status": "<string>"
},
"id": "<string>",
"lead_reference": "<string>",
"mode": "<string>",
"model": "<string>",
"playbook_revision": 123,
"policy_digest": "<string>",
"priority": "<string>",
"priority_reason": "<string>",
"rationale": "<string>",
"revisit_at": "2023-11-07T05:31:56Z",
"rule": "<string>",
"source": "<string>",
"stage": "<string>",
"status": "<string>",
"undone_by_external_ref": "<string>"
}{
"code": "not_found",
"details": {},
"message": "<string>"
}RecordLeadNextActionAPI records a person's hold or close on one lead. It
replaces the lead’s open suggestion and withdraws any card that suggestion filed.
There is deliberately no MCP tool for this: a hold or close is the sales desk’s call about a person it is talking to. A scoped API key is refused.
curl --request POST \
--url https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"kind": "<string>",
"decided_by_external_ref": "<string>",
"note": "<string>",
"until": "2023-11-07T05:31:56Z"
}
'import requests
url = "https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions"
payload = {
"kind": "<string>",
"decided_by_external_ref": "<string>",
"note": "<string>",
"until": "2023-11-07T05:31:56Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
kind: '<string>',
decided_by_external_ref: '<string>',
note: '<string>',
until: '2023-11-07T05:31:56Z'
})
};
fetch('https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions', 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/datasets/{datasetSlug}/leads/{leadRef}/next-actions",
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([
'kind' => '<string>',
'decided_by_external_ref' => '<string>',
'note' => '<string>',
'until' => '2023-11-07T05:31:56Z'
]),
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/datasets/{datasetSlug}/leads/{leadRef}/next-actions"
payload := strings.NewReader("{\n \"kind\": \"<string>\",\n \"decided_by_external_ref\": \"<string>\",\n \"note\": \"<string>\",\n \"until\": \"2023-11-07T05:31:56Z\"\n}")
req, _ := http.NewRequest("POST", 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.post("https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"kind\": \"<string>\",\n \"decided_by_external_ref\": \"<string>\",\n \"note\": \"<string>\",\n \"until\": \"2023-11-07T05:31:56Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/datasets/{datasetSlug}/leads/{leadRef}/next-actions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"kind\": \"<string>\",\n \"decided_by_external_ref\": \"<string>\",\n \"note\": \"<string>\",\n \"until\": \"2023-11-07T05:31:56Z\"\n}"
response = http.request(request)
puts response.read_body{
"accompanying_email": {
"body_markdown": "<string>",
"error": "<string>",
"execution_ref": "<string>",
"sent_at": "2023-11-07T05:31:56Z",
"status": "<string>",
"subject": "<string>",
"to": "<string>"
},
"action_input": {},
"action_kind": "<string>",
"approval_request_id": "<string>",
"canonical_lead_id": "<string>",
"created_by": "<string>",
"dataset_id": "<string>",
"decided_at": "2023-11-07T05:31:56Z",
"decided_by": "<string>",
"decided_by_external_ref": "<string>",
"due_at": "2023-11-07T05:31:56Z",
"error": "<string>",
"evaluated_at": "2023-11-07T05:31:56Z",
"executed_at": "2023-11-07T05:31:56Z",
"execution_ref": "<string>",
"facts": {
"bedrooms": {
"source_column": "<string>",
"value": "<string>"
},
"broker": {
"source_column": "<string>",
"value": "<string>"
},
"budget": {
"source_column": "<string>",
"value": "<string>"
},
"financing": {
"source_column": "<string>",
"value": "<string>"
},
"language": {
"source_column": "<string>",
"value": "<string>"
},
"purpose": {
"source_column": "<string>",
"value": "<string>"
},
"timeline": {
"source_column": "<string>",
"value": "<string>"
}
},
"handoff_alert": {
"error": "<string>",
"execution_ref": "<string>",
"sent_at": "2023-11-07T05:31:56Z",
"status": "<string>"
},
"id": "<string>",
"lead_reference": "<string>",
"mode": "<string>",
"model": "<string>",
"playbook_revision": 123,
"policy_digest": "<string>",
"priority": "<string>",
"priority_reason": "<string>",
"rationale": "<string>",
"revisit_at": "2023-11-07T05:31:56Z",
"rule": "<string>",
"source": "<string>",
"stage": "<string>",
"status": "<string>",
"undone_by_external_ref": "<string>"
}{
"code": "not_found",
"details": {},
"message": "<string>"
}Authorizations
An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).
Body
Response
Success response
out beside a gated decision: whether it went, when, in what words, and why it did not if it did not.
It joins the row's own columns (the authoritative state) with the draft stored in action_input, so a caller does not have to read two things and work out which one is true.
Show child attributes
Show child attributes
handles for the person who decided this action and the person who took that decision back — opaque strings Erdo stores verbatim and never interprets.
They exist because an application built on `/v1` authenticates a whole team with ONE organization API key, so DecidedBy — the Erdo account that key belongs to — is the same person for every decision anybody there makes. The caller is the only party that can say which of its users acted, so it says, and it resolves these back to names on its own side.
ATTRIBUTION, NEVER AUTHENTICATION. Anything holding the organization's key can write any value here; Erdo does not verify it, must never branch on it, authorize by it, or resolve it against an Erdo identity. It is fine for "Approved by Ana" on a card and must never be presented as proof of who acted. Empty means the caller offered no name, which is every decision made before this existed.
means the lead has not told us.
Show child attributes
Show child attributes
desk a handoff is waiting: whether it went, when, the message to open, and why it did not go if it did not.
The alert IS a handoff's delivery. Nothing else happens when a handoff is carried out — deliverHandoff records the literal string "accepted" and sends nothing — so a surface that wants to say what a handoff DID has to read this and not the row's execution ref. It is also why these facts are exposed at all: a sent alert is a real, openable message, and the timeline used to show a handoff as an unopenable line that mentioned no email whatever.
It reads only the row's own columns, unlike AccompanyingEmailState, because the alert's words are not drafted onto the decision — they are composed at send time from the card by buildHandoffAlert. The message itself is read back from the email service by ExecutionRef, which is the only copy of it there is.
Show child attributes
Show child attributes
is what says whether the decision is stale — not the revision number. The revision bumps on every save, so a decision at revision 9 may have been decided under exactly the rules revision 10 carries; comparing it with LeadPlaybook.PolicyDigest answers the question the revision only hints at. Empty on every decision written before digests existed and on one a person recorded by hand. See policy.go for the two halves it is made of.

