# Commands

A command links request acceptance to processing results. Inspect it after enrolling leads or stopping a lead.

> **Note.** Write access is enabled for your organization on request.

## Read a command

`GET /v1/commands/{id}` returns HTTP 200 and a [command object](https://docs.melioralab.io/en/reference/commands/). 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.

```bash
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. Inspect `result.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](https://docs.melioralab.io/en/enroll-leads/#rezultat).

For a stop, `items=[]`. `result` contains `automation_state` (`stopped` or `stopping`), `send_in_flight` and `unknown_outcome_message_ids`. See [stopping a lead](https://docs.melioralab.io/en/stop-lead/#rezultat). 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](https://docs.melioralab.io/en/rate-limits/#povtor-bezopasen), which handles 429/503. The 60-poll bound stops waiting but does not cancel the command: persist its ID to continue later.

```python
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}")
```
```js
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](https://docs.melioralab.io/en/events-polling/): 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.
