> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erdo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Lead next action

> Write a development's sales playbook in plain text and let Erdo keep one suggested next action per lead: an email, a card for the sales desk, a wait or a close.

# Lead next action

A development's leads sit in a leads dataset, and each one needs somebody to
decide what happens next: ask for a budget, offer a meeting, pass a question to
the sales desk, or leave the lead alone. The lead playbook is how you tell Erdo
those rules. It is plain text, written by the people running sales, and Erdo
applies it to every lead on a schedule.

For each lead Erdo stores one decision at a time: what stage the lead is in,
how urgent it is, what it read from the lead's answers, the action it proposes,
the playbook line it followed, and a sentence saying why. Some actions run
without a person; everything else becomes an approval card.

## The playbook

The playbook is free text. A useful one describes the lead stages, what makes a
lead high or low priority, which actions Erdo may take and when, a follow-up
schedule, and the working hours:

```text theme={null}
Actions
- email (automatic): plain email in the lead's language, signed by the sales
  desk. Use it to ask for budget and timeline, or to offer meeting times.
- handoff (approval): when a lead asks something the desk must answer.
- wait: nothing to do yet; say until when.
- close: when they say stop, are a test, or are unresponsive.

Follow-up schedule
- No response 3 days after our first email: second email, shorter, one question.

Working hours: Monday to Friday 9:00 to 18:00, New York time.
```

An organization that has never saved a playbook reads the default template.
Nothing runs until a playbook is saved and enabled.

### The read-back

When you save a playbook, Erdo reads the text once into a typed read-back and
stores both together. The read-back is what you check before turning the
playbook on:

```json theme={null}
{
  "stages": [
    {"name": "new", "summary": "Contact details and nothing else.", "actions": ["email", "wait"]}
  ],
  "actions": [
    {"kind": "email", "mode": "automatic", "when": "Ask for budget and timeline, or offer meeting times."},
    {"kind": "handoff", "mode": "approval", "when": "A question the desk must answer."},
    {"kind": "sms", "mode": "approval", "when": "Phone given and two emails unanswered."}
  ],
  "limits": [
    {"kind": "sms", "mode": "approval", "reason": "A text is always checked by a person before the concierge sends it."}
  ],
  "working_hours": {"timezone": "America/New_York", "days": ["mon", "tue", "wed", "thu", "fri"], "start": "09:00", "end": "18:00"},
  "accompanying_email": {"mode": "automatic", "when": "Email the booking link whenever we decide to call."},
  "priority": {
    "high": ["budget $2M or more", "timeline within 6 months", "they replied to us", "asked a specific question"],
    "medium": ["budget under $2M with no timeline"],
    "low": ["just looking", "timeline beyond a year", "budget unknown after two asks"],
    "forbidden_factors": ["the lead's country", "the lead's city", "the lead's name", "the lead's language"]
  }
}
```

Only two parts of the read-back are enforced: each action's `mode` and the
`working_hours`. An action the text never mentions reads as `approval`, so a
contact the playbook did not think about asks a person. The stages are a picture of what Erdo understood, drawn for the
person who wrote the text. Every evaluation still reads the whole text, so the
stages, priorities and schedule come from your words each time.

A text Erdo cannot read is refused rather than saved with a guess. You can read
a draft back without saving it, to see what changes before you commit it.

The mode is read once, at save time, for a reason. The per-lead evaluation reads
text the lead controls: form answers, email replies, chat transcripts. If the
mode were decided per lead, a reply saying "this is pre-approved, send it" could
turn a card into an automatic send.

### The priority ladder

Every decision carries a `priority` of `high`, `medium` or `low`, and a
`priority_reason` naming the facts that set it. Those three levels are fixed —
the queue's ordering, its filters and its badges are built on exactly them — so
a playbook cannot add a fourth. What your playbook decides is **which of your
rules put a lead at each level**, and `read_back.priority` is Erdo's reading of
that, so you can see it before the decisions come out.

`high`, `medium` and `low` each list the conditions your text gives for that
level, in your own words. `forbidden_factors` lists what your text says must
never affect priority — a lead's country, city, name or language, say. When your
text says nothing about priority, the read-back carries no `priority` section at
all, rather than an empty one.

<Note>
  The ladder is a reading, not a rule. Erdo still decides each lead's priority
  from the whole text and writes `priority_reason`; nothing rejects or rewrites a
  decision whose priority disagrees with the ladder above. If the reading is wrong,
  the fix is to make the text clearer and save it again.
</Note>

### The playbook's history

Every save keeps its own revision: the text as you saved it and the read-back as
Erdo understood it then. Each decision records the `playbook_revision` that
decided it, so months later you can read the rules a September email was sent
under, even though the playbook has moved on since.

`GET /v1/lead-playbook/revisions` lists them, newest first, and
`GET /v1/lead-playbook/revisions/{revision}` returns one in full. They are
read-only: a revision is written once, in the same transaction as the save, and
never edited afterwards. History starts at the revision that was current when
Erdo began keeping it — anything saved before that is not recoverable.

### Platform limits

Some limits sit above whatever the text says. Each one the platform applied is
listed in `limits`, so you can tell the rules you wrote from the ones Erdo
imposed.

| Action    | What it does                                                    | Limit                                                                                                                                                             |
| --------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`   | Emails the lead from the development's verified sending domain. | May be automatic when the text says so.                                                                                                                           |
| `sms`     | A text message to the lead, from the concierge.                 | `never` unless a concierge is linked. With one, at most `approval`: the decision carries the drafted message and a person says yes, because nothing sends it yet. |
| `call`    | A phone call to the lead, from the concierge.                   | `never` unless a concierge is linked. With one, at most `approval`: the decision carries the call brief and a person says yes, because nothing places it yet.     |
| `handoff` | Asks the sales desk to act personally.                          | Always a card, because its purpose is a person.                                                                                                                   |
| `wait`    | Nothing to do until a time.                                     | Contacts nobody.                                                                                                                                                  |
| `close`   | Stop working the lead.                                          | Contacts nobody and deletes nothing.                                                                                                                              |

An email also becomes a card when the lead has no email address or when the
development has no verified sending domain.

**Texts and calls need the lead's yes.** Erdo texts or calls a lead only when
the form they submitted asked whether they may be called and texted, and they
said yes (`consent_given` is `true` on their row). A lead who said no, and a
lead whose form never asked, are emailed instead, or handed to the desk when
Erdo has no email address for them. The check is made when the decision is
taken and again just before the text or call goes out, so a card approved
earlier does not ring a lead whose row no longer says yes. A lead who later
submits the form again and says yes can be texted and called from then on.
How the form asked does not matter, only the answer.

When the text says an action is `never`, a proposal of it is not filed at all,
not even as a card. The decision is stored as a wait with the reason, and the
lead is looked at again in seven days.

### The concierge

A playbook can name one **concierge**: the voice agent that already answers this
development's website widget, its phone and its texts. Link one and the same
person your leads have been talking to is who writes their follow-ups.

Three things change when a concierge is linked:

* **Every draft is written as that concierge.** Its persona — the same text the
  widget and the SMS replies speak from — says who is writing and how they
  sound, and the playbook's `signature_name` signs the email. The playbook still
  decides what may be said; the persona never overrides it.
* **Drafts are grounded in what the development has published.** Before each
  decision Erdo reads the organization's public knowledge for what this lead has
  been asking about, so a follow-up can answer a question from last week's chat
  instead of repeating the playbook. Facts taken from there are not treated as
  unsupported figures. A knowledge outage costs the grounding, never the
  decision.
* **Texts and calls become possible to propose.** Without a concierge they read
  back as `never` and are not filed at all, because nothing is named to carry
  them out; the `limits` say so. They are still approval-gated with one, and
  approving one records the decision — status `approved` — which the delivery
  step then carries out as the concierge.

### What the concierge is told to do

A decision about an email, a text or a call carries `agent_instructions` in its
`action_input`: an `objective` saying what the action is for, and `instructions`
saying what to say, what not to say, and in what register. For a call that block
is the call brief. For a text the decision also carries `sms.body`, the message
as the lead would receive it — plain text, at most 320 characters; a longer
draft is dropped rather than trimmed and the `downgrade_cause` says so.

```jsonc theme={null}
"action_input": {
  "agent_instructions": {
    "objective": "Get her to pick one of the two viewing slots.",
    "instructions": "Say who you are. Offer Thursday 11:00 or Friday 16:00. Do not quote a price."
  },
  "sms": {"body": "Hi Carla, Marina from 2200 Brickell. Thursday 11:00 or Friday 16:00 for a viewing?"}
}
```

A text or a call carries no `handoff` block at all: it is not work for the desk,
and a card that showed both a desk task and a finished message said two
contradictory things about one decision. `handoff.summary` belongs to a genuine
handoff, where a person deals with the lead personally; the instructions are
addressed to the concierge that carries a text or a call out. They go through the same figure check as an email body, so an automatic
email whose brief states a price the playbook and the published material do not
becomes a card, and a text card whose brief does says so in its
`downgrade_cause`. The approval card shows the draft and the brief.

Link a concierge by passing its `agent_id` when you save the playbook. List the
organization's concierges with `GET /v1/voice/agents` or `erdo voice agents`.
Unlike `enabled` and `daily_contact_cap`, `agent_id` is **not** kept when you
leave it out: a save with no `agent_id` clears the link.

### The name a lead sees

`signature_name` is the name your leads meet: a follow-up email is signed and
sent as it, and a call brief opens with it.

It is separate from the concierge's own name because the two are chosen for
different readers. A voice agent's name is the label you pick to tell one agent
from another in a list — "Outbound Re-engagement Agent" is useful there and odd
to receive an email from — and the persona written for that agent often
introduces it to leads by a different name again. Set `signature_name` to the
name the concierge introduces itself by, and the email signature, the From
header and the call brief all agree.

Leave it empty and nothing changes from how the engine has always behaved: the
concierge's own name signs the draft, and with no concierge linked the draft is
unsigned and the email goes out under the organization's sending name.

Unlike `agent_id`, `signature_name` **is** kept when you leave it out, like
`enabled` and `daily_contact_cap`. Clearing a concierge link is a deliberate
choice a picker offers; a name is free text, and a client that does not know the
field must not be able to strip the name a development signs its mail with. Send
an empty string to clear it back to the concierge's name.

The name is at most 120 characters and must be a single line — it becomes an
email's From header.

```bash theme={null}
curl -X PUT https://api.erdo.ai/v1/lead-playbook \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "...", "agent_id": "…", "signature_name": "Sofia"}'
```

Each saved revision records the `signature_name` in force when it was written,
so a decision that cites a revision can still say which name it went out under.

`GET /v1/lead-playbook` returns both `agent_id` (what was saved) and `agent`
(what it resolves to now). They are the same thing except when the concierge has
been archived, where `agent_id` is still set and `agent` is `null` — that pair
is how you can tell an archived concierge from none at all. An archived
concierge stops texts and calls immediately, without re-saving the playbook.

Everything about a playbook with no concierge works exactly as it always has:
email, wait, close and handoff are unchanged.

### Telling the desk a lead needs a person

A **handoff** is Erdo saying it cannot answer this one — so nothing reaches the
lead, and the decision waits as an approval card. A card nobody opens expires
after three days, and Erdo then leaves that lead alone for a week. Someone who
asked a direct question can therefore sit in silence for ten days while nobody
at the desk knows the question was asked.

`handoff_alert_recipients` is what stops that. Every address on the list is
emailed the moment a handoff is filed — not when it is approved, because
approving means somebody has already found the card, and finding it is the
problem. The email says which lead, what Erdo could not answer, why it became a
handoff, and how to reach them, and it links straight to the follow-up queue.

**An empty list means nobody is told.** That is the default and it is a real
setting, not a gap: Erdo has no fallback audience for this and never emails the
organization's members instead. A development that has not filled this in gets
the behaviour it has always had, where the card waits in the queue until
somebody opens it.

Addresses are lowercased, de-duplicated and sorted, and at most ten people can
be on the list. An entry that is not an email address is refused and the save
does nothing, rather than being dropped quietly — silently discarding a typo is
how somebody comes to believe they are on the alert list when they are not.

Like `signature_name`, the list **is** kept when you leave the field out, so a
client that does not know about it cannot take a development's desk off its own
alert list. Send an empty list to stop alerting anybody.

```bash theme={null}
curl -X PUT https://api.erdo.ai/v1/lead-playbook \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "...", "handoff_alert_recipients": ["desk@example.com", "sales@example.com"]}'
```

Unlike every other playbook setting, the list is **not** frozen into each saved
revision. Who gets told is a delivery detail rather than something a decision
was made under, so it is read when the alert is sent: change the list and the
next handoff — including ones already waiting — goes to the new list, with no
need to re-save the playbook.

The alert is an internal message and is treated as one. It does not count
against the daily contact cap, it does not wait for working hours, and it is
never signed as the concierge — all three are promises about what reaches a
lead, and this reaches your own staff.

### Working hours

Nothing Erdo sends goes out outside the playbook's working hours — an email, a
text or a call. Anything decided outside them waits and goes out when the next
window opens; it is never failed for being out of hours. A text or a call also
has to pass the organization's own quiet hours and consent rules, and a message
those hold back waits in the same way. When the text states no working hours
Erdo can read, the default is Monday to Saturday, 08:00 to 19:00, New York time,
and the read-back lists that under `limits`.

#### A text and a call follow the lead's own clock

Your working hours are your development's — "Monday to Saturday, 08:00 to 19:00,
New York time". An email is sent in those hours, because an email waits in an
inbox until the lead opens it.

A phone is different: it rings at the moment the message is sent. So **for a text
and a call, Erdo keeps your days and your hours but reads them where the lead
is.** A lead in Madrid is texted between 08:00 and 19:00 Madrid time, not between
08:00 and 19:00 in New York — which would have reached them at two in the
morning. You do not configure this and there is nothing to set: it follows from
the hours you already wrote.

Erdo works out where a lead is from the lead's own record, and never from their
name or the language they wrote in:

1. The timezone recorded when the lead was captured, which is the usual case.
2. The country on the lead's record, where that country keeps one clock.
3. The country code of the phone number they gave, if they wrote it with a `+`.

**When none of those answers, your own working hours are used unchanged**, and
the decision says so in as many words — so a card for a call tells you which
clock it was timed in, including when that clock is yours because Erdo could not
tell where the lead is.

### The daily contact cap

The daily contact cap bounds how many emails Erdo sends, or puts in front of a
person as email cards, in any 24 hours. It defaults to 40 and can be set from 0
to 500. A slot is spent when the email actually goes out, so an email decided on
Friday evening takes Monday's slot rather than Friday's; one over the cap waits
where it is and goes out when a slot opens. The cap stops a first run over
hundreds of older leads from emailing all of them in one morning. Handoff cards
do not count toward it.

### How often one lead is contacted

The first thing Erdo tries to find out about a new lead is whether they can be
reached at all. A lead who has never answered on any channel — no reply, no
call, no chat, no text, no booking — is **unconfirmed**, and is worked harder
and faster, close to when they were captured. The first answer of any kind, on
any channel, makes them **confirmed** for good, and the slower, ordinary pace
takes over.

Erdo keeps a gap **per channel**, so an email this morning and a text this
afternoon are both possible, while two emails inside the email gap are not:

|                              | email    | sms      | call     |
| ---------------------------- | -------- | -------- | -------- |
| Before the lead has answered | 4 hours  | 2 hours  | 4 hours  |
| After the lead has answered  | 48 hours | 48 hours | 48 hours |

A proposal inside a channel's gap is stored as a wait until that channel
reopens. A lead who responded since our last message on a channel can always be
answered at once.

Your playbook can set any of these. Write them in plain language — "while a new
lead has not answered, email at most every two hours and text at most every
hour; once they reply, never email twice in the same week; contact a new lead
thirty minutes after they come in" — and Erdo reads them back to you before any
lead is touched. Erdo will not go below its own minimums (30 minutes between
emails, an hour between texts, three hours between calls, five minutes after a
lead is captured); where your text asks for less, the read-back says so and the
minimum applies. Five minutes after capture is also the default, so a playbook
that says nothing about it gets in front of a new lead as soon as their record
has finished being written.

Two limits hold whatever the playbook says:

* After four messages in a row with no response, another email is always a
  card, even when the playbook makes email automatic. This does not change while
  a lead is unconfirmed: four unanswered messages are an answer.
* Working hours, consent, quiet hours, opt-outs and do-not-call are untouched by
  any of this. Contacting sooner never means contacting outside them, and texts
  and calls are still always checked by a person.

How many follow-ups to send, in what order, and when to give up belong in the
playbook's follow-up schedule.

## The sweep

Every 15 minutes Erdo goes through the leads datasets of every organization with
an enabled playbook. For each lead it:

1. Skips test rows, and leads captured in the last 5 minutes, so a capture has
   finished writing the lead's record before it is read. Your playbook can set
   that grace, up to a week; Erdo will not go under five minutes. A new lead does
   not wait for the next sweep — a capture wakes Erdo directly, and the lead is
   decided as soon as its grace is up.
2. Decides whether the lead needs a new decision. A lead is evaluated when it
   has never been evaluated, when its row or timeline changed since the last
   decision, or when the last decision's revisit time has passed. Nothing else
   triggers a model call.
3. Evaluates the lead against the playbook and stores the decision.
4. Sends an automatic email, files a card, or records a wait or close.

One sweep makes at most 200 evaluations across all organizations. Leads never
evaluated go first, then the ones looked at longest ago, and the rest wait for
the next sweep.

Revisit times keep a quiet lead moving without re-reading it every 15 minutes:

| After                                         | The lead is looked at again                                                                                                               |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| An email sent, or a handoff approved          | 48 hours later, or sooner if the lead changes — and on the next sweep while the lead has still never answered and another channel is open |
| A wait                                        | At the time the wait names, or 24 hours later when it names none; never more than 30 days out                                             |
| A failed evaluation                           | An hour later                                                                                                                             |
| A rejected text, call or email                | The next sweep, on the other channels; the rejected channel stays shut for 7 days unless the lead responds                                |
| A rejected desk task, or an expired card      | 7 days later, unless the lead changes first                                                                                               |
| An approved action dropped before it went out | The next sweep                                                                                                                            |
| A close or a hold                             | When the lead gets in touch again, or when a hold's time is up                                                                            |

## Rule citation

An automatic email must quote the playbook line it follows, and Erdo checks
that the quote really occurs in the text. Case, line wrapping and list markers
do not matter; a paraphrase or an invented rule counts as no rule.

The check exists because every evaluation has to propose something. Where the
playbook is silent, a model would otherwise fill the gap from general sales
sense, and an automatic email that follows no rule you wrote is exactly what the
playbook is meant to prevent. So an email that cites no real line becomes a
handoff card, and a text or a call that cites none stays a text or a call a
person must approve; either way the rationale starts with `No playbook rule was
cited:`. Gaps in the text therefore reach a person as cards, and the desk adds a line to
the playbook when the situation will come up again. A wait or a close needs no
citation, because neither contacts anyone.

## The figure check

An automatic email is checked for figures: prices, dates, times, unit numbers,
any number. A figure that does not appear in the playbook, the development's
name or the lead's own row turns the email into an approval card, with the
reason recorded as `downgrade_cause`. Links are ignored. The playbook is the only
source of facts an automatic email may state, so a delivery date or a price the
model supplied is checked by a person before it reaches a lead.

## Approval cards

An action that needs a person is filed as an ordinary approval request, with
the lead as its subject. It appears in the [approvals](/approvals) queue with
the message, the brief or the desk task in full and the rationale as its
context.

* **Approving records the decision; it does not send.** Every approved card —
  email, text, call or handoff — lands on status `approved`, and a delivery step
  running every two minutes carries it out. It sends exactly what the card
  showed: nothing is rewritten between the card and the send.
* **Before it goes out**, the decision is checked against the lead as it is now.
  If the lead answered you, was closed or held by the desk, or a newer decision
  was filed, the action is `superseded` and nothing is sent. The row says which
  of those it was, in `error`, and a decision somebody had approved says so —
  an approval that does not go out is never dropped silently. A column changing
  on the lead's record is not one of these reasons: it makes Erdo look at the
  lead again, and it does not cancel a step you already approved.
* **A text or a call is carried out by the concierge**, from its own number,
  subject to the organization's consent, opt-out, do-not-call and quiet-hours
  rules. `execution_ref` names the message or the call that really happened. If
  the concierge has been archived or unlinked, the action `failed` and says so —
  Erdo never sends as nobody.
* **Approving a handoff card** records that the desk has taken it. Nothing is
  sent.
* **You can take an approval back** until the moment it is carried out:
  `POST /v1/datasets/{slug}/leads/{lead}/next-actions/undo` puts the decision
  back in front of a person and records who took it back. If the action has
  already gone out the call is refused rather than pretending to recall it.
* **Rejecting** a text, a call or an email shuts that one channel for this lead
  for 7 days. Erdo decides again on the next sweep and goes on with the
  playbook's other channels, so turning down a text does not also stop the
  email the playbook sends next. A reply from the lead lifts it early.
* **Rejecting** a desk task, or letting any card expire after 72 hours, stops
  Erdo proposing that lead again for 7 days, unless something about the lead
  changes first: a reply, a call, a new capture, or an edit to its row.
* **Editing the playbook** withdraws cards nobody has answered yet and decides
  those leads again. An approved card stays approved: it goes out under the
  playbook's current hours and gaps, unless the edit now forbids that kind of
  contact altogether.
* **A newer decision withdraws the older card**, so an email that a later
  evaluation replaced can no longer be approved.

Standing approval policies never approve these cards. The saved playbook
already decided they need a person.

## An email alongside a text or a call

A text and a call always wait for a person. An email does not. So a decision to
call a lead used to file a card and leave that lead with nothing at all until
somebody at the desk acted on it — which may be three days, since a card expires
after 72 hours, and then another week of quiet.

**A decision that needs a person can now carry an email that goes out at once
beside it.** Erdo writes it in the same evaluation that chose the call, sends it
immediately, and the card still waits for you. The lead hears from us within
minutes of arriving; the phone call still has a person in front of it.

It is one decision, not two. There is still exactly one open suggestion per
lead, one card, and one row in the lead's history — the email rides on it.

### Whether your playbook sends one

Write it in your playbook text and Erdo reads it back:

* "When you decide to call a lead, email them the booking link as well" turns it
  on.
* "Do not email a lead you are also calling" turns it off.
* Say nothing, and Erdo sends one **when your playbook already lets it email a
  lead without approval**, and never otherwise. An unanswered call leaves the
  lead nothing; an email leaves something they can read and a link they can use.
  But if you have not given Erdo permission to email a lead unattended, it does
  not get that permission by attaching the email to something else.

The read-back shows the answer, and says so in `limits` when your text asked for
one your playbook does not allow:

```json theme={null}
{
  "accompanying_email": {"mode": "automatic", "when": "Email the booking link whenever we decide to call."}
}
```

### What it obeys

Exactly what an automatic email obeys, and in the same words: your daily contact
cap, the gap Erdo keeps between two emails to one lead, the ceiling on unanswered
messages, your working hours, your verified sending domain, the lead's consent
and opt-out, and the check that stops Erdo stating a price or a date your
playbook does not. If any of those says no, **the card is still filed** — it just
says on it that the lead has not heard from us.

### On the card

The card names the call you are approving and says, above the brief, whether the
lead has already been emailed — with the subject and the words that went. You
are approving the call, not the email; the email has gone and cannot be recalled.
A card that says the email has not gone yet is telling you the lead is still
waiting in silence.

### What it will not do

* **It never goes twice.** Approving the call carries out the call; the email
  that already went does not go again, and one that had not gone is dropped —
  the call is reaching them instead.
* **Undo does not unsend it.** Taking an approval back puts the call in front of
  a person again; the email the lead already read stays on the record, and the
  new card still shows it.
* **A replaced decision does not pretend it did nothing.** If a reply arrives and
  a newer decision supersedes this one, an email that already went stays on the
  record and the concierge is told about it before it calls. One that had not
  gone is dropped.

Read it back on any decision as `accompanying_email`:

```json theme={null}
{
  "accompanying_email": {
    "status": "sent",
    "to": "ana@example.com",
    "subject": "Your two-bedroom enquiry",
    "body_markdown": "Hi Ana, …",
    "sent_at": "2026-09-16T14:02:11Z",
    "execution_ref": "em_…",
    "error": ""
  }
}
```

`status` is `pending` (drafted, not sent yet, with `error` saying what it is
waiting for), `sending`, `sent`, `failed` (a rule said no permanently) or
`skipped` (the decision moved on first). It is absent on every decision that
carries none.

## Hold and close

The sales desk works leads outside Erdo too: calls from its own phones, emails
from a colleague's inbox. Two manual decisions tell Erdo so:

* **Hold** means the desk is handling this lead. It lasts seven days by default,
  at most 180, and ends early when the lead gets in touch again.
* **Close** means stop working this lead. Erdo suggests nothing more until the
  lead gets in touch again.

Both replace the lead's open suggestion and withdraw any card it filed, so Erdo
does not email somebody the desk is already talking to. Both are stored as
decisions, so the lead's history shows who held or closed it and why.

## Statuses

| Status             | Meaning                                                                                                                                                                                                                                                                                             |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `queued`           | Filed and on its way to a person: a card is being raised for it, or an approval was taken back and a new card is being filed.                                                                                                                                                                       |
| `pending_approval` | A card is waiting for a person.                                                                                                                                                                                                                                                                     |
| `approved`         | Decided and waiting to be carried out — by the playbook itself, or by a person who said yes. `decided_by` and `decided_at` are set when a person decided it; `executed_at` and `execution_ref` stay empty, because nothing has happened yet. This is the window in which an approval can be undone. |
| `executing`        | The action is being carried out right now.                                                                                                                                                                                                                                                          |
| `executed`         | It happened. `execution_ref` names the sent email, the text, the call, or `accepted` for a handoff the desk took.                                                                                                                                                                                   |
| `rejected`         | A person rejected the card.                                                                                                                                                                                                                                                                         |
| `expired`          | The card expired or was withdrawn in the approvals queue.                                                                                                                                                                                                                                           |
| `superseded`       | Closed before it was carried out, so nothing was sent. `error` says why: a newer decision about the same lead was filed, the lead answered you first, or the lead is no longer in the dataset. Where a person had approved it, the wording says so.                                                 |
| `failed`           | The evaluation or the delivery did not happen; `error` says why — the lead opted out, the concierge is gone, there is no number, the send was refused.                                                                                                                                              |
| `recorded`         | A wait, a close or a hold. Nothing runs.                                                                                                                                                                                                                                                            |

## The timeline and the dry run

The timeline is everything the evaluation reads about one lead: the dataset
row, the lead's email addresses and phone numbers, and every entry in time
order. Entry kinds are `capture`, `email`, `email_reply`, `sms`, `call`, `chat`,
`booking` and `suggestion`. Every entry is dated when the thing happened, so a
`booking` carries the moment the lead picked a time and names the meeting itself
in its summary — the meeting is also given on its own as the next or last
meeting in the timing facts. It also carries the timing facts a schedule rule is
matched on, computed by the platform rather than by the model: days since
capture, days since our last message and since the lead's last reply, how many
of our messages in a row went unanswered, the next or last meeting, and whether
it is now inside working hours. Two readings of the hours are given: the
development's, which an email follows, and `phone_hours`, the same hours read
where the lead is, which a text and a call follow. `lead_zone` beside them says
which timezone that is and how Erdo worked it out — or that it could not, and
your own hours are being used. A source that could not be read is listed under
`unavailable`, and the evaluation is told that source is unknown.

The dry run evaluates one lead now and returns the decision with the read-back
and timeline it used. Nothing is stored and nothing is sent. Pass a draft
playbook to see what a change would do on a real lead before you save it;
without one, the dry run uses the saved playbook and refuses when none is saved.

## Permissions

Reading the playbook, the suggestions, a timeline and the dry run take
organization membership. Saving the playbook takes an organization admin.
Scoped API keys, the per-end-user credentials, cannot save the playbook or hold
and close leads.

## REST API

A lead is addressed by its `canonical_lead_id` UUID or its 22-character lead
reference, as on [lead identity](/lead-identity). The dataset is addressed by
slug.

| Endpoint                                                      | Purpose                                                                                                                                                                                                                                                                            |
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /v1/lead-playbook`                                       | The saved playbook, its read-back, whether it is enabled, the daily contact cap, and the linked concierge (`agent_id` and the resolved `agent`).                                                                                                                                   |
| `PUT /v1/lead-playbook`                                       | Save the text. `enabled` and `daily_contact_cap` keep their saved values when omitted; `agent_id` does not — leaving it out clears the link.                                                                                                                                       |
| `POST /v1/lead-playbook/read-back`                            | Read a draft back without saving it. `agent_id` reads it back as saving it with that concierge would.                                                                                                                                                                              |
| `GET /v1/lead-playbook/revisions`                             | Every saved version, newest first: `revision`, `enabled`, `daily_contact_cap`, `agent_id`, `saved_by`, `saved_at`, plus the `total` kept. Paged with `limit` (default 20, maximum 100) and `offset`.                                                                               |
| `GET /v1/lead-playbook/revisions/{revision}`                  | One saved revision in full, including the `body` and the `read_back` as they were. `404` when that revision was not kept.                                                                                                                                                          |
| `GET /v1/voice/agents`                                        | The organization's concierges, for picking one: `id`, `slug`, `name`, `phone_number`, `lead_dataset_id`.                                                                                                                                                                           |
| `GET /v1/lead-next-actions`                                   | Each lead's current suggestion, highest priority first. Filters: `dataset`, `status`, `priority`, `limit`, `offset`.                                                                                                                                                               |
| `GET /v1/datasets/{slug}/leads/{lead}/next-actions`           | Every decision about one lead, newest first.                                                                                                                                                                                                                                       |
| `POST /v1/datasets/{slug}/leads/{lead}/next-actions`          | Record a `hold` or a `close`.                                                                                                                                                                                                                                                      |
| `POST /v1/datasets/{slug}/leads/{lead}/next-actions/undo`     | Take back an approval that has not been carried out, and put the decision back in front of a person. `action_id` names one decision (omit for the lead's current one); `note` says why and is carried onto the refiled card. `412` once the action has been delivered or replaced. |
| `GET /v1/datasets/{slug}/leads/{lead}/timeline`               | The lead's timeline and timing facts.                                                                                                                                                                                                                                              |
| `POST /v1/datasets/{slug}/leads/{lead}/next-actions/evaluate` | Dry run, optionally with a draft `body`.                                                                                                                                                                                                                                           |

Save and enable a playbook:

```bash theme={null}
curl -X PUT https://api.erdo.ai/v1/lead-playbook \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Actions\n- email (automatic): ...", "enabled": true, "daily_contact_cap": 20, "agent_id": "8d4c6f21-0e3b-4a77-9f52-6b1c8e0d4a33"}'
```

```json theme={null}
{
  "exists": true,
  "body": "Actions\n- email (automatic): ...",
  "read_back": {"stages": [], "actions": [{"kind": "email", "mode": "automatic", "when": "..."}], "limits": [], "working_hours": {"timezone": "America/New_York", "days": ["mon", "tue", "wed", "thu", "fri"], "start": "09:00", "end": "18:00"}},
  "enabled": true,
  "daily_contact_cap": 20,
  "revision": 3,
  "agent_id": "8d4c6f21-0e3b-4a77-9f52-6b1c8e0d4a33",
  "agent": {"id": "8d4c6f21-0e3b-4a77-9f52-6b1c8e0d4a33", "slug": "front-desk", "name": "Marina", "phone_number": "+13055550100"},
  "updated_at": "2026-09-15T14:02:11Z"
}
```

An `agent_id` that is not a live concierge of your organization is refused with
`400` and a message saying which of the three things is wrong: the id is
malformed, the concierge belongs to another organization, or it has been
archived.

List the cards waiting for a person:

```bash theme={null}
curl "https://api.erdo.ai/v1/lead-next-actions?status=pending_approval&priority=high" \
  -H "Authorization: Bearer $ERDO_API_KEY"
```

```json theme={null}
{
  "next_actions": [
    {
      "id": "5b0f…",
      "lead_reference": "0YQ0mBGRmvJRWWrDsPBQaB",
      "source": "playbook",
      "stage": "qualified",
      "priority": "high",
      "priority_reason": "Budget known and ready to buy now.",
      "facts": {"budget": {"value": "$1M–$2M", "source_column": "budget_range"}, "timeline": {"value": "Ready now", "source_column": "timeline"}},
      "action_kind": "handoff",
      "action_input": {"handoff": {"summary": "Asked whether the two-bedroom on floor 12 is still available."}},
      "rule": "handoff (approval): when a lead asks something the desk must answer.",
      "rationale": "The lead asked about a specific unit, which the desk must answer.",
      "mode": "approval",
      "status": "pending_approval",
      "approval_request_id": "c41e…"
    }
  ],
  "limit": 50,
  "offset": 0
}
```

Hold a lead the desk is calling:

```bash theme={null}
curl -X POST https://api.erdo.ai/v1/datasets/acme-leads/leads/0YQ0mBGRmvJRWWrDsPBQaB/next-actions \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "hold", "until": "2026-09-22T09:00:00-04:00", "note": "Desk is calling them this week"}'
```

The response is the stored decision, with `action_kind` `hold`, `status`
`recorded` and `revisit_at` set to the end of the hold.

Try a draft playbook on one lead:

```bash theme={null}
curl -X POST https://api.erdo.ai/v1/datasets/acme-leads/leads/0YQ0mBGRmvJRWWrDsPBQaB/next-actions/evaluate \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "…draft playbook text…"}'
```

```json theme={null}
{
  "evaluation": {
    "stage": "new",
    "priority": "medium",
    "action_kind": "email",
    "action_input": {"to": "lead@example.com", "email": {"subject": "When are you planning to buy?", "body_markdown": "…"}},
    "rule": "No response 3 days after our first email: second email, shorter, one question.",
    "rule_verified": true,
    "rationale": "The welcome email went out 3 days ago with no reply.",
    "mode": "automatic",
    "proposed_kind": "email"
  },
  "read_back": {"stages": [], "actions": [], "limits": []},
  "timeline": {"entries": [], "timing": {"days_since_last_outbound": 3, "unanswered_outbound_in_a_row": 1}}
}
```

## MCP tools

MCP clients can read and try the playbook, but cannot change what Erdo does to
leads:

| Tool                                | Purpose                                                                     |
| ----------------------------------- | --------------------------------------------------------------------------- |
| `erdo_get_lead_playbook`            | The saved playbook and its read-back.                                       |
| `erdo_read_lead_playbook`           | Read a draft back without saving it.                                        |
| `erdo_list_lead_playbook_revisions` | Every saved version of the playbook, newest first.                          |
| `erdo_get_lead_playbook_revision`   | One saved revision in full: the text and the read-back as they were.        |
| `erdo_list_lead_next_actions`       | Each lead's current suggestion, with the same filters as REST.              |
| `erdo_get_lead_next_actions`        | Every decision about one lead.                                              |
| `erdo_get_lead_timeline`            | One lead's timeline and timing facts.                                       |
| `erdo_evaluate_lead_next_action`    | The dry run, optionally with a draft playbook and a concierge to try it as. |
| `erdo_list_voice_agents`            | The organization's concierges, with the `id` a playbook links.              |

Saving or enabling the playbook, and holding or closing a lead, deliberately
have no MCP tool. Enabling a playbook that marks email automatic lets Erdo email
real people without anyone looking, and a hold or close is the desk's judgement
about somebody it is talking to. A person makes those decisions over REST or the
CLI.

## CLI

```bash theme={null}
erdo voice agents
erdo leads playbook get
erdo leads playbook read-back --file playbook.txt --agent-id <id>
erdo leads playbook set --file playbook.txt --enable --daily-contact-cap 20 --agent-id <id>
erdo leads playbook revisions --limit 20
erdo leads playbook revision 4

erdo leads next-actions list --status pending_approval --priority high
erdo leads next-actions history acme-leads <lead>
erdo leads next-actions evaluate acme-leads <lead> --file draft-playbook.txt --agent-id <id>
erdo leads next-actions hold acme-leads <lead> --until 2026-09-22T09:00:00-04:00 --note "Desk is calling"
erdo leads next-actions close acme-leads <lead> --note "Bought elsewhere"

erdo leads timeline acme-leads <lead>
```

Every command takes `--json` for the raw response.

## Related

* [Lead identity](/lead-identity)
* [Approvals](/approvals)
* [Sending domains](/sending-domains)
