# Status history

History records actual status changes. Use it to recover their order as well as to understand the current value.

New features are enabled for your organization on request.

## Three routes

- `GET /v1/leads/{id}/status-history`: a lead's participation history in a campaign; requires `leads:read`.
- `GET /v1/campaigns/{id}/status-history`: campaign history; requires `campaigns:read`.
- `GET /v1/campaigns/{id}/accounts/{account_id}/status-history`: account history in the selected campaign; requires `accounts:read`.

Account history does not expose activity in other campaigns. The key's workspace and campaign scope applies. An out-of-scope entity returns 404, just like a missing entity.

```bash
curl -G https://api.melioralab.io/v1/leads/5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31/status-history \
  -H "Authorization: Bearer $MELIORA_API_KEY" \
  -d limit=50
```

Each route accepts `limit` and `cursor`. The default is 50 records and the maximum is 100. Transitions are ordered by ascending `entity_revision`, not notification delivery time. A cursor lasts 15 minutes. On 410, start again and merge records by `id`. See the [history reference](https://docs.melioralab.io/en/reference/status-history/) for full responses.

## Transition records

Every item in `data` has `object=status_transition` and includes:

- `id`: the transition ID; `entity_kind`: `lead`, `campaign` or `account`; `entity_id`: that entity's ID.
- `workspace_id`, `campaign_id`, `lead_id`: context. `lead_id` may be `null` in campaign and account history.
- `entity_revision`: the entity's transition sequence number, starting at 1. Use it to order changes within one entity's history.
- `from_status`, `to_status`: the previous and new public statuses.
- `occurred_at`: when the change happened; `recorded_at`: when it was saved.
- `reason_code`: `operator_action`, `schedule`, `queue_exhausted`, `recipient_not_found`, `telegram_auth`, `subscription`, `recovery` or `unspecified`.
- `source`: who changed the status: `user` (a cabinet user), `system` (service automation), `support` (Meliora support) or `unknown`. This is informational, not an instruction to the client.

An unavailable reason is returned as `unspecified`; an unavailable source is `unknown`. Saving the same status does not create another transition. History describes changes to the status field, not every action on the entity.

## Public statuses

For leads: `new`, `contacted`, `completed`, `replied`, `blocked`, `failed`, `suspended`, `deleted`, `opted_out`, `unknown`. See [lead statuses](https://docs.melioralab.io/en/lead-states/#status) for their meanings.

For campaigns: `draft`, `scheduled`, `active`, `paused`, `stopped`, `completed`, `archived`, `launching`, `failed`, `paused_no_credits`, `unknown`. See [campaign statuses](https://docs.melioralab.io/en/lead-states/#campaign-status).

For accounts in history:

- `online`: connected; `offline`: disconnected.
- `limited`: restricted; `banned`: banned.
- `deleted`: deleted; `locked`: access locked; `unauthorized`: authorization required.
- `paused`: paused; `unknown`: the value could not be determined.

Unrecognized source values become `unknown`. This is valid for both `from_status` and `to_status`; do not turn it into `failed` or `offline`. Account history includes `paused`; the ordinary account list may represent that value as `unknown`.

Statuses can return to earlier values: for example, `paused` can change to `active`. A rule that only allows statuses to move forward is not suitable for this history.

## Initial snapshot and completeness

`meta.baseline` contains a separate `status_baseline` object with `status`, `entity_revision`, `recorded_at`, entity context and `provenance=baseline`, or is `null`. Its revision may be 0. This is an observed initial status, not a transition from an invented previous value. It does not produce a `*.status_changed` event.

History boundaries are in `meta`:

- `history_started_at`: when history collection began. Earlier changes are not reconstructed.
- `available_from`: the beginning of the available history.
- `retained_from`: the beginning of the retained period.
- `observed_at`: when the server read the data.

Unknown boundaries are `null`. Expiration of older records must not be mistaken for complete history since the entity was created.

`meta.completeness.status` is `complete`, `partial` or `unknown`. It also includes `gaps`, `gaps_truncated` and `incomplete_from`. Each gap has `id`, `from`, `to` and `state`; states are `open`, `recovering`, `recovered`, `unrecoverable` and `unknown`. The list is limited to 50 intervals; `gaps_truncated=true` means it was shortened. `incomplete_from` helps identify earlier incompleteness; `null` alone does not prove there are no gaps.

`complete` refers to the available history period, not the entity's entire lifetime. For `partial` or `unknown`, preserve the incomplete state, reconcile the current record and read history again later. `has_more=false` only means there are no more pages.

## Status change events

The feed supports `lead.status_changed`, `campaign.status_changed` and `account.status_changed`. Select the [2b catalog](https://docs.melioralab.io/en/events-polling/#catalog-2b) or one explicit new `type`.

`data` includes `organization_id`, `workspace`, `campaign`, `transition_id`, `entity_kind`, `entity_id`, `from_status`, `to_status`, `reason_code`, `source`, `occurred_at` and `recorded_at`. `transition_id` matches the transition's `id` in history. An account event belongs to one campaign; it does not list other campaigns.

`lead.status_changed` only reports a status change. `lead.replied` reports a counted business reply: each replying lead is counted once per campaign. Do not increment reply counts from `lead.status_changed` or every `message.received`; the latter represents one individual incoming message. A single reply can produce different event types describing the same interaction.

Deduplicate repeated deliveries by `event.id`. Notifications can arrive after the change itself. To recover the order, read history by `entity_revision` and match `transition_id`. Reconcile the record for its current state. Do not choose the final status based only on webhook arrival order.

[New webhooks](https://docs.melioralab.io/en/webhooks/#new-event-types) of these types carry metadata only. A status change does not include message text.
