CLI
@erdoai/cli wraps the same /v1 API the MCP server exposes, so
anything an AI assistant can do over MCP you can do (and script) from a shell.
Install
Requires Node.js 22.13 or later.erdo integrations connect --from-connection for credentials
with a verified authenticated supplier. See reusing a connection’s credential.
Update
npm install -g @erdoai/cli@latest.
When a newer version is published, commands print one line telling you so:
ERDO_NO_UPDATE_NOTIFIER=1 to
silence it.
The notice matters more than it looks: an old build’s --help is accurate about
itself and wrong about the product, so a command added since your install simply
isn’t listed — and nothing else tells you it exists.
Auth & orgs
Accounts are stored at~/.config/erdo/config.json, one active at a time.
ERDO_API_KEY, ERDO_ORG, ERDO_API_URL, ERDO_ACCOUNT.
Pin the org in automation
The active org set byerdo org use lives in machine-global config, so it is
shared across every concurrent session — another shell (or another job) running
org use switches the org for all of them. A command that only reads is
harmless, but a mutation that lands in the wrong org is not, so any command that
writes prints the org it is about to act in on stderr before it fires:
erdo --org <id|slug> <command> or by setting ERDO_ORG, so the command acts in
the org you intended regardless of what any concurrent session did. Runs that
build real artifacts enforce this: erdo eval run refuses an artifact-building
suite unless the org is pinned (see Evals).
API tokens
An API token is an account-level credential — it acts as you in any org you belong to, not just one. The org stored on a token is only its default (used when a request names no org);erdo --org <x> <command> or erdo org use <x>
steers any command to another of your orgs, and the backend re-checks your
membership on every request.
ERDO_API_KEY, or authenticate an interactive session with
erdo login --key <token>. There is deliberately no way for an AI assistant
(MCP) to mint tokens — creation lives only on the CLI/REST surface a human drives.
A --read-only token can read through the API in every org you belong to but
cannot change anything, start agent runs or mint tokens. Use it for monitoring
and automation that only needs to look. See
Scoped API keys.
Manager accounts
A manager account operates many client (“managed”) orgs with one credential — the pattern a portal uses to provision and run an org per customer without a pasted key per tenant. Your active org is the manager (you must be an admin/owner of it);erdo org managed create provisions a client org, and erdo org managed key mints
a single manager key that acts inside any managed org via --org <slug>.
erdo org managed performance is the one read a portfolio dashboard needs: per client
and per UTC day, the advertising provider’s spend, impressions, clicks and conversions
beside page visits, form starts, on-page lead events and the leads that actually reached
the client’s leads dataset. A measurement whose source dataset is missing or unreadable
prints as - and is explained under the table — it is never reported as zero.
The manager key is one non-expiring credential for every org you manage — target a
specific one with erdo --org <slug> (or the X-Organization-ID header). Running
erdo org managed key again rotates it.
adopt brings an org that already exists under your management, and it needs the
other side’s consent: the token comes either from that org’s owner offering it, or
from its current manager handing it on with handoff-token. add-member reports
whether the person was seated immediately or sent an invitation, because no Erdo
account existed for that address yet. See Manager accounts
for the full portal flow and the REST/MCP surface.
Project context
Use the global--project flag when a command should run inside an Erdo
project. It takes the project’s slug or its UUID. Pair it with --org whenever
the token can operate more than one organization:
ERDO_PROJECT is the environment-variable equivalent for CI. Commands that
take a project of their own, such as workstream create and experiment create, read the same flag, so erdo workstream create --project launch-2026
and erdo --project launch-2026 workstream create are one and the same.
Saved Shortcuts
Inspect a saved Shortcut’s complete Knowledge record, then run its current saved prompt, agent, datasets and outgoing linked skills:--no-wait prints its startup acknowledgement; it does not confirm completion.
--sync holds one request until completion and can exceed proxy timeouts.
These commands use the existing GET /v1/knowledge/{id} and POST /v1/ask
with saved_ask_id and optional parameters. Scoped keys need resources:read
for inspection and agents:run for launch; saved-resource access is also checked.
If the saved Shortcut declares project_id, the authenticated active project
must match it. Select it with --project <slug-or-id>; missing or different scope
fails before creating a thread. A saved configuration cannot widen a scoped key’s
project authorization.
Each launch creates a fresh thread. A lost response is ambiguous and is never
automatically replayed: inspect agent runs before starting another launch.
A successful run does not itself prove that a workflow’s dataset writes finished;
read the workflow’s canonical output records to verify that outcome.
Agents
Running an agent is sending it a message; the artifact-builder produces pages this way.--hidden, which keeps it
out of the organization’s thread list. You can still open it by id.
erdo agent delete permanently removes a thread and its messages. You need
admin on the thread: you created it, or you are an organization admin. Like
every command it acts on the active org, or the one named by --org.
erdo agent threads lists your own threads by default. Pass --scope org for
every thread in your organization, or --scope shared for threads shared with
you (excluding your own) — both require org membership, so a B2B2C scoped
token (which is never an org member) gets a permission error rather than a
silently empty or partial list. --order-by activity sorts by last message
time instead of creation time (--scope org/shared only; ignored for your
own threads). Automation- and heartbeat-sourced threads are excluded by
default — add --include-automation to see them. Page through results with
--limit/--offset (default 20, max 100); the CLI reports the total shown
on stderr so a page never reads as the complete list.
Use --context when an application or script knows state that should guide one
turn but should not masquerade as the operator’s words in the transcript. The
agent receives it as application context; the thread still shows the exact
message argument as the user message. For longer instructions, ask and send
accept @path in place of their message argument and read the UTF-8 file as the
visible message. Prefix a literal message or context with @@ when it must begin
with @ (for example, @@alice please review this sends @alice please review this). send --context @path reads application context from a UTF-8 file while
keeping it out of the visible message. The one-shot ask endpoint
does not accept separate application context; include everything the agent needs
in its question file.
ask and send start the run, print its thread id, and poll until it finishes.
Agent runs routinely take minutes — building a landing page, screening variants —
and a single HTTP request held open that long is cut off by the edge proxy, so
polling is the default. The thread id is printed before the wait begins: if you
interrupt the CLI, or the run pauses for approval, the work carries on server-side
and you pick it back up by thread.
erdo approvals decide <id> --approve;
erdo agent wait <thread> then picks the wait back up and prints the answer when
the run completes. Approval resumes apply only to the run’s current paused request;
replaying an older approval does not restart the current attempt. Resuming an
existing run requires edit access to that run or its thread. Safe crash recovery
is bounded to five attempts; a run interrupted during an uncheckpointed mutation
is not automatically replayed. Recovery does not guarantee that an external
provider call executes exactly once. An existing nonterminal pause without a verified
current approval cannot be resumed safely: the caller receives a refusal, and the
run records that refusal once no live executor owns it (preserving any existing
diagnostic). Review its existing result before starting a new authenticated request.
An approval decided after its run has already ended retains the existing late-approval
policy, which may start a new run; it does not reopen the ended run.
Recovery and approval continuations retain the initiating credential’s
restrictions and project selection. A revoked, expired or changed key can block
continuation. Automatic recovery records the reason; a manual continuation
reports the refusal before execution. Retry a temporary verification error, or
start a new explicitly authorized attempt after an access refusal. Reattaching with wait or approving an action
does not change the run owner’s authority. See recovery and access.
Pass --sync to hold one request open instead. It returns faster on quick
questions, and it will time out on anything long.
Cancel a running invocation
--invocation <id> to name one explicitly. If another invocation has started on the thread since, it refuses with “the run already changed” and stops nothing. It requires edit access to the thread; a scoped API key also needs agents:run. A successful response ({"success": true}) acknowledges propagation to the execution services. Read the run with erdo runs get <run-id> or wait for the thread to confirm its terminal state. A propagation failure exits with an error.
Pressing Ctrl+C while ask, send, or wait is polling only closes the local command. Use cancel to request that execution stops.
Pages & artifacts
list shows your pages newest first (id, created-at, visibility, title) and filters by title substring (--query) or a created-at window (--created-after / --created-before, RFC3339). Pass --type <type> to list any artifact type (charts, tables) instead of just pages. delete is a soft delete — the page’s public link stops working and it drops out of list, but restore brings it back (private, since deleting revoked its public grant).
clone copies a page: same content byte for byte, and the lead-form
event pipelines the source owns are duplicated onto the copy
with the references in its content rewritten, so the copy captures its own leads
instead of filing them against the page it was copied from. The copy is
private whatever the source’s visibility — publish it explicitly when it’s
ready. Omit --title to get Copy of <source title>; pass --json for the full
response including the cloned pipelines and the id rewrites. See
Cloning a page.
--html/--js/--css accept @path to read a file.
update edits an existing page in place (the URL and id stay the same) and merges the fields you pass, so you only send what’s changing. It takes the same content flags as deploy — --title, --html, --js, --css (plus the --datasets / --writable-datasets / --kv / --writable-kv grants below) — but all are optional: pass just --js to swap the script while keeping the existing HTML, CSS, and title. --public / --private change visibility. (Runtime is fixed at create time, so there’s no --runtime on update.)
--meta-title / --meta-description set the page’s public SEO metadata — the browser-tab title and the shared-link/search-result preview — distinct from --title, which is the internal name shown in your workspace. Omit them to leave the current value alone; on update, pass an empty string (--meta-title "") to clear one back to the default.
--meta-icon-asset / --meta-image-asset set the page’s public favicon and share-card image — a knowledge attachment id, typically from the org’s Brand Style record (apple-icon-180x180 / favicon-32x32 for the icon, og-image or a hero photo for the share image). Same omit-to-keep / ""-to-clear semantics as --meta-title on update. Leave both unset and the page falls back to Erdo’s own favicon and share card.
A page’s first content and a full rewrite are reviewed by the judge lenses automatically; anything smaller is your call. Pass --request-review for a material edit to a live page — a restructured section, compliance or legal copy, anything touching the form, the CTA, or layout — and the findings plus any available mobile Lighthouse categories print after the update. A null category prints as unavailable, never 0. Skip it for a cosmetic tweak:
--datasets / --kv grant read (for window.erdo.queryDataset / erdo.kv.get); --writable-datasets / --writable-kv grant write (for erdo.insertRows / erdo.kv.set):
erdo.submitEvent) instead — see Build Apps.
Custom page domains
Published pages serve from Erdo’s pages host until you register a custom domain. A domain belongs to the organization, not to one page: once it is active every public page the org publishes is reachable on it, and share links are minted on it.add takes a direct subdomain of a domain you control (a root domain cannot carry
the CNAME) and comes back pending_dns with the records someone has to create at
your DNS provider — the records go to stdout as type, name, value, pasteable
or pipeable, with the instruction around them on stderr. list shows how far each
domain has got: pending_dns, pending_cert, active, failed (with the reason)
or disabled, and when the status was last reconciled, so a domain stuck part-way
through is visible for what it is.
transfer moves a domain to another organization you also administer, keeping its
certificate — serving flips with no re-validation downtime, which is what makes it
the right move at a cutover rather than removing and re-registering.
manager-default is for a manager account: it marks one of
your domains as the one the organizations you manage serve their pages from
when they have registered none of their own, so your clients’ pages carry your
hostname without each client validating a domain. A client that did register its own
domain always keeps it — the default is a fallback, never an override. At most one
domain carries the flag, so naming a second one moves it; --unset clears it and
those pages return to the Erdo host. list marks the current one manager default,
and add --manager-default registers and points in one step.
Agent runs
Inspect what agents have done (the runs behind ask/send/evals).--status combines with an agent or thread filter and is applied before
--limit. Lists contain root runs; use a run’s steps to inspect its sub-agents.
--invocation selects the exact root run for one invocation, rather than the
latest completed turn on a thread. An optional agent or thread filter constrains
that identity; it does not select a different run. An unavailable or inaccessible
invocation returns no rows; multiple root runs are an error.
--resources answers “what did this run actually follow”: every skill, memory and
entity the run used, grouped by kind, with how each one arrived beside it — pinned
for the organization, swept in by the ambient search an agent runs before its first
turn, or fetched by the agent itself part-way through. A skill that arrived both ways
shows both, because “we handed it over” and “the agent went looking for it” are
different facts about the same run.
--steps prints the run’s shape as a tree: each model turn, the tools it called, and
the sub-agents it handed work to, with the agent key and the child run id so you can
follow the work down. Timings are included; message history and tool payloads are not —
that stays out of a visibility read.
--json prints the full public response with whichever resources and steps you
requested. Resource IDs and stored payload_json evidence remain available for
readback and scripts. It uses the same permissions and API response as the
summary. With neither include flag, the command prints the run as JSON by
default.
Approvals
Some agent actions pause for a human decision. List them and approve/reject so the paused run can continue. See Approvals.Decisions
What your organization committed to, whether the change actually happened, and what the evidence said afterwards. See Decisions.decisions show prints the alternatives the proposing agent weighed, the before-state each action
replaced, and the lineage: the workstream it was filed under, the escalation that raised it, and
the approval card somebody answered. The same report is a page in the app at
/activity/decisions/<slug>.
Attention
The digests, choices and escalations awaiting a human. See your attention feed.--item prints the item’s report: what happens if nobody answers and when, the decision the
escalation became with what it predicted and what has been measured, the approval card, and the
earlier items about the same thing. The same report is a page at /activity/attention/<slug>.
Review queue
The knowledge agents propose, the investigations they open, and the failure signals they keep counting — the queue of things awaiting a human decision. See the review queue.Bounded outreach
Prepare and audit consent-gated SMS to the exact audience an operator selected. Each batch accepts 1 to 700 explicit recipients; Erdo never chooses the first N or creates automatic batches. Outreach is always project-scoped, so every command requires the global--project flag (or ERDO_PROJECT). Creating a batch only
produces a reviewable draft; act ... arm is a separate compare-and-set action
that starts contact only when its preview revision, eligible count, and message
hash still match.
--file. A consent declaration
contains its evidence; revocation contains {"grant_ref":"...","revoked":true}.
A batch file contains its source snapshot, initiative, sequence step, reviewed
message and the exact 1 to 700 selected recipients. List and recipient reads
return opaque next_cursor values; pass one back with --cursor until the
complete selection has been read.
batches list --initiative <ref> filters at the server before pagination, so a
workspace never mixes batches from unrelated outreach programmes.
For pause, resume, and cancel, pass the source snapshot, state, and timestamp
from the same batches get response. Retry an uncertain request with the exact
same values and action reference; stale or altered lifecycle clicks fail closed.
See Bounded outreach for the full JSON examples, exclusion reasons,
provider readiness, lifecycle, MCP tools, and scoped-key capabilities.
Datasets
list shows the newest 20 datasets by default; when more exist it says so on
stderr. Use --limit (max 100) and --offset to page through the rest.
upload accepts CSV, TSV, Excel, JSON, JSONL, PDF, DOCX, TXT, Markdown, and
more — the extension drives type detection. The schema is extracted before the
command returns, so the printed slug is immediately usable. Larger files (over
20 MB) go through the web app’s resumable upload.
The dataset vocabulary
Every dataset can carry a purpose — its role in your organization, likeleads or page events. One purpose maps to one dataset, so the vocabulary is
the deterministic answer to “which dataset holds our leads” without searching by
name:
datasets create makes an empty dataset whose columns are declared in --schema, which is required unless the purpose is one whose columns Erdo defines (page events, widget events, widget leads). It refuses a second dataset for a purpose the org already has, naming the one to reuse; --separate deliberately runs a parallel table.
datasets ensure is the producer setup command: it creates or reuses the one dataset for the purpose and verifies the declared schema in the same operation. The schema file is either {"columns": [...]} or the columns array itself. Use --preferred-dataset to adopt an existing stored binding without replacing its history. For the leads purpose, a newly created dataset gets permanent lead identity, and --email-column/--phone-column (default email/phone) name which of your declared columns hold the contact evidence the resolver reads — at least one must be in your schema. A dataset that already existed keeps capturing exactly as it did: Erdo does not turn identity on underneath the forms already writing to it, and instead migrates it — those forms and its identity together — the next time a row lands. A legacy split or incompatible populated schema is reported as a migration conflict rather than silently choosing a table.
Repeat --reviewed-optional-addition <name> for producer-reviewed optional columns that are present in the schema file. This atomically widens the existing unique purpose-bound file dataset after the complete contract is rechecked; ordinary ensure remains strict.
Erdo sets some purposes for you. Connecting an advertising account gives the
dataset it syncs the purpose ad spend, page tracking gives its dataset
page events, and a lead capture pipeline gives its dataset leads — so a
dashboard or an agent can find that data the day it starts arriving, with nobody
declaring anything. You can still move one: erdo datasets set-purpose clears
and re-sets, which is how you correct a purpose that landed on the wrong dataset.
The second column is the current write target. A legacy split is printed explicitly as (split: N datasets) and an empty vocabulary entry as (unbound); Erdo never presents the historical establishing dataset as the current target. --scope managed
is for a manager account: it lists every client organization’s vocabulary at
once, with the organization as the first column, and the dataset slugs it prints
are already organization-qualified — so you can read a client’s leads without
switching organizations. It is for the manager organization’s admins and owners,
the same people who can switch into a client; anybody else is told so. An
organization that manages no clients is told so rather than shown an empty list.
Write rows
write accepts a JSON array of objects, inline or from @path, and prints the
server’s affected, inserted and updated row counts. An empty array is a zero-row
no-op. With no key and no explicit mode, rows append. Either explicit
--write-mode replace or --write-mode merge requires a non-blank key. A key
must name at least one non-blank column even when the write mode is omitted. It
makes file and writable PostgreSQL table writes an upsert. New keys insert.
File datasets replace matching rows and clear omitted fields. PostgreSQL updates
the non-key columns present in the batch: columns absent from every row stay
unchanged, and a missing cell in a column supplied by another row becomes NULL. Comma-separated
keys match a composite key. ClickHouse-backed writes
append even with --key-column and --write-mode replace; they refuse merge
mode.
--write-mode merge fills blank fields while preserving existing values.
--overwrite-columns names fields a non-blank incoming value may replace in merge
mode. Populated canonical lead datasets refuse generic writes; use their capture
pipeline or supported lead operations. Writes are never automatically replayed
after a network failure, so inspect the dataset before retrying.
The CLI preserves JSON values in the request. File datasets store cells as text;
objects and arrays do not round-trip as JSON. Supply JSON strings for file columns
that need parseable structured content. Write dataset rows
covers storage support, permissions and the declared column contract.
Reading a dataset
There are two reads, and picking the right one matters. Before writing SQL, see what columns are actually there:schema (also describe) reads physical stored columns by default. Use
--declared to read the declared column names, required flags, type hints,
semantic roles and descriptions before writing rows. A missing contract is
reported explicitly; it does not substitute physical columns. With
--declared --json, the raw response is printed, with the contract under
declared_schema. Both reads go through the dataset’s fetch endpoint (the
declared read passes view: "declared_schema"), so an API key scoped to
datasets:query for that dataset can run either.
See Extend a dataset’s schema for the JSON request and the
steps to verify a new form field reaches the stored lead.
schema reads the stored table itself (a DuckDB DESCRIBE), so it shows the
physical columns your SQL can reference — including ones added by recent writes
that a declared schema may not list yet. --json prints the raw result.
fetch is the deterministic read. You write the SQL, so the same command
returns the same rows every time — use it for anything mechanical, scripted, or
run by an agent. It answers with columns, rows, and row_count.
data — for file datasets, and
for anything an event pipeline writes, that is its name regardless of the
dataset’s slug. Database and warehouse datasets are queried through their real
table names, which come from the dataset’s schema. A dataset’s default filters
apply to every read; --filter <name> adds a saved filter on top and narrows
further, never bypassing a default. erdo datasets filter list <slug> shows the
names a dataset offers.
query is the natural-language read. Erdo writes and runs the SQL for you
and answers with that SQL alongside the values, so reach for it when you don’t
yet know the shape of the data. It runs an agent, so it is slower, and two
identical questions can produce two different queries.
fetch — the same SQL you would run against the live table.
revisions prints each version’s id, when it was created, when it was
superseded (blank for the live one, and for a stored file that was never live),
and the stored file’s name and type; --json prints the raw result.
Dataset revisions covers what does and does not create
one. The history is read-only — nothing here rolls a
dataset back. To restore, fetch the rows out of the old revision and write them
in again through the normal write path.
Integrations
Connect third-party apps and data sources from the terminal. Which flow you get depends on whether you hold the credential, not on whether the app is one of Erdo’s native integrations or one of the thousands in the SaaS catalog — see Connecting integrations for the full picture. Anything authenticated by a secret you already have — a database password, an API key, a service-account JSON — connects in one command with-c key=value
(repeat it per field). That covers native integrations and catalog apps whose
auth type is keys. OAuth apps are the exception: the provider mints their
credentials during the authorization itself, so there is nothing to pass. They
reject -c with an explanation and, connected without it, print a
connect_url for the browser; status then confirms the result.
-c starts out visible only to you; --share makes it
visible to the whole organization instead, which is usually right for an
org-level credential teammates and agents build on. disconnect releases the
stored credential and removes the connection — organization admins can
disconnect any of the org’s connections, not just their own, so a credential
connected by someone who has since left is still removable.
Every connect names the organization the credential attaches to, because that
org is resolved from the pin rather than typed and a connection made in the
wrong one is invisible until it starts syncing. The name and slug come back in
the response as organization_name / organization_slug and are printed above
the authorize URL, so an OAuth connection can still be abandoned at that point.
erdo integrations connect-links create says the same thing about the
connection its recipient will make.
A native credential integration is verified against the provider before the
command returns, so active means Erdo has genuinely talked to it. A catalog
keys app is only stored — the connector platform saves what it is given
without calling the vendor, and the printed next_step says so; the first action
you run is what confirms the key.
status on an app you have never connected answers normally — connected: false
with an empty list. It is a fact about your account, not a failure, so don’t read
it as one.
Once a database is connected, make it queryable as a dataset — queries run
live against the source, nothing is copied. Pass the schemas to include (some
integrations allow only one); tables lists the selectable ones. Per-table
column listing is available for SQL databases (Postgres, MySQL, and
compatible); warehouses list schemas only.
--disable-sync. It tears down the synced copy and stops the
dataset being read or monitored. Restoring it is --enable-sync on its own,
which keeps the segment scope the dataset was already syncing. Because the
retirement deletes the synced copy rather than pausing it, restoring builds a new
one — so the first sync after a restore is a full load, not an incremental one.
Automations keep the datasets they were set up with, so a monitor watching the
retired dataset would go on reading it. Add --replaced-by with the slug of the
dataset that takes over, and every automation that reads the retired dataset is
moved onto it. The replacement must be in the same organization.
Knowledge
Knowledge is your agents’ shared brain — definitions, skills, and learnings.update requires at least one field. Omitted title, body, and config stay as
read. --config replaces the entire config object, including tags and skill
code, so include everything you want to keep. If an entry changes between the
read and save, the CLI returns HTTP 409; read the entry again before retrying.
Entries are workspace (organization-internal) by default; public opts an
entry into anonymous external surfaces such as the
website voice widget — a draft is approved in the same step
and goes live immediately.
See Knowledge visibility.
KV (collections)
Named KV stores (collections) are Erdo’s shared key/value store — the canonical config and values (pricing, targets, brand tokens) that pages read, Knowledge bodies reference as{{slug.key}}, and agents resolve. One store, consistent everywhere.
Event pipelines
Inspect the flows that capture leads and write their destination datasets:enabled, site_key, secret, and hostnames fields. Omit --turnstile
to preserve the policy. On an enabled update, omit secret to keep the encrypted
existing secret. Protection is off by default; passive observations
continue independently and do not rank leads by IP address. See
event pipelines for other update fields and execution records.
Automations
Automations run on a schedule — either an agent that reasons each tick, or a zero-LLM script that runs a deterministic check. See Automations.update changes only the fields you pass. Edit an agent automation’s prompt with --instructions, or a scripted automation’s body with --script-js / --script-file — sending the wrong one for the automation’s kind is rejected rather than silently ignored. list shows each automation’s kind (agent or script).
executions reads one exact job ID through GET /v1/heartbeats/:id/executions,
including a dataset refresh job or a retired job whose history still exists and
you can view. It prints the API’s {executions, total} JSON unchanged: each
execution has id, status, started_at, and optional completed_at and
thread_id. total is the number returned in this bounded response, not a
pagination count. The default limit is 10; invalid limits fail before a request.
There is no offset, predecessor aggregation, or recovery of deleted history.
An empty list is shown only when the API returns one. Permission, missing-job,
and service errors exit nonzero.
hello@your-domain, signed by your domain — and, if you turn receiving on,
replies come back where an agent can read them.
pending_dns with the
records someone has to create at your DNS provider, and until it reads active
every send still goes out from Erdo’s address. add and get print the
records as type, name, value on stdout — pasteable, or pipeable to whoever
runs the DNS — with the status notes on stderr. An org has one sending domain, so
switching means removing the current one first.
--receiving true|false decides whether replies come back into Erdo, and
--forward-to names the real mailbox each one is copied to. A domain that
already routes mail to Google Workspace or Microsoft 365 is registered
sending-only whatever you pass, because Erdo’s MX record cannot share the name —
add says so rather than issuing a record that would break your mail.
Read what people wrote back:
--json
prints the raw response.
Voice calls and widget conversations
Read the phone conversation records your voice agents keep — the calls people place to an agent’s own number, and the calls Erdo places for you:list prints the call id, direction, both numbers, status, duration, whether a
transcript was captured, who placed the call by hand and what became of the
lead’s side of it (both - on the AI agent’s own calls — see Calling a
Lead), the caller’s details and what became of them, and
the short summary; --json prints the raw response. When more pages exist the next
cursor is written to stderr, so a piped stdout stays clean. get returns the
rendered transcript, the raw transcript, and the per-turn model latency and
token usage.
Website chat widget conversations are a separate surface — they belong to a
widget rather than a phone number (see Website Concierge) — and
they read the same way:
list prints the session id, which widget recorded it, the modality, duration,
whether a transcript was captured, what the visitor left, what became of it, and
the short summary. The counts on stderr say how many of the matching total you
are looking at, so a page cut short never reads as a complete answer.
The lead a conversation produced
Both lists carry acontact column and a lead column. contact is what the
person told the agent about themselves — a name and the best email or phone —
extracted from the transcript once the summarizer has read it. A conversation
nobody has read yet shows -; one that was read and left nobody reachable shows
none. lead is what was then done with those details:
In
--json output the same fields appear as contact (null when unread, an
object with blank fields when read and nobody was reachable), lead_status
(empty string while pending) and lead_saved_at.
Evals
Workstreams
Workstreams track multi-step business work — campaigns, lead engines, comms loops — with phases, an event log, and overall state.detach prints removed_count, so a call that matched no link answers 0 rather than reporting
success — the difference between a stale link cleared and an id typed wrong.

