Commands
A command links request acceptance to processing results. Inspect it after enrolling leads or stopping a lead.
#Read a command
GET /v1/commands/{id} returns HTTP 200 and a command object. Take the ID from the enrollment or stop 202 response. Weight is 1; GET needs no body or Idempotency-Key.
For kind=enroll the key needs leads:enroll; for kind=stop it needs leads:stop. leads:read alone is insufficient. Organization and scope are checked against the original campaign and workspace, even after the lead record is deleted or moved. Another organization's key cannot read the command.
curl https://api.melioralab.io/v1/commands/bd465c91-d715-4987-ab61-e63a9f01c625 \
-H "Authorization: Bearer $MELIORA_API_KEY"#States
accepted: saved, awaiting processing.running: processing in progress.done: processing complete. For enrollment, inspect every item: there may be refusals and holds. This state does not confirm message sending.done_with_unknown: the stop settled but some past send outcomes are unknown. Inspectresult.unknown_outcome_message_ids; do not retry those sends.failed: command processing ended in failure. Save the command and item reasons for diagnostics; reconcile leads first, then decide which refused entries to correct and submit as a separate operation.
finished_at is set on completion and is null before it. For a stop, cleanup_pending indicates unfinished cleanup. Reading a command does not send anything or resume a sequence.
#Results
For enrollment, result.accepted and result.inserted are counters and items contains per-lead results in input order with zero-based input_index. status (accepted, existing, rejected) records intake. queue_state (waiting, queued, held, failed, stopped, not_required) tracks enqueue. See adding leads.
For a stop, items=[]. result contains automation_state (stopped or stopping), send_in_flight and unknown_outcome_message_ids. See stopping a lead. The command returns no first message text and cannot modify accepted fields.
#Polling
Poll one command with a delay, for example 2 seconds, without parallel loops. Retry GET after 429/503 using Retry-After plus jitter; stop on 401/403 and correct access. Do not repeat POST with a new key just because processing is pending.
These examples use meliora() from limits and retries, which handles 429/503. The 60-poll bound stops waiting but does not cancel the command: persist its ID to continue later.
terminal = {"done", "done_with_unknown", "failed"}
command_id = "bd465c91-d715-4987-ab61-e63a9f01c625"
for _ in range(60):
command = meliora(f"/commands/{command_id}")
if command["state"] in terminal and not command["cleanup_pending"]:
print(command)
break
time.sleep(2)
else:
raise TimeoutError(f"Command still pending: {command_id}")const terminal = new Set(['done', 'done_with_unknown', 'failed']);
const commandId = 'bd465c91-d715-4987-ab61-e63a9f01c625';
let settled = false;
for (let i = 0; i < 60; i++) {
const command = await meliora(`/commands/${commandId}`);
if (terminal.has(command.state) && !command.cleanup_pending) {
console.log(command);
settled = true;
break;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
if (!settled) throw new Error(`Command still pending: ${commandId}`);After enrollment reaches done, inspect lead states and events: only confirmed sending produces message.sent or lead.first_message_sent. After stopping, wait for lead.stopped; it may be published after the command response.