---
title: "List the steps of an automation run"
method: GET
path: "/companies/{company}/automations/runs/{run}/steps"
tags: ["Automations"]
---

# List the steps of an automation run

`GET /companies/{company}/automations/runs/{run}/steps`

## Request

- Base URL: `https://api.factuarea.com/v1`
- URL: `https://api.factuarea.com/v1/companies/{company}/automations/runs/{run}/steps`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

List the steps of a run in execution order with cursor pagination.
- Same step documents as the run detail, without the two heavy frozen snapshots; cheaper to poll.
- `step_index` identifies a step: it is what you replay.

## Path parameters

- `company` string, uuid, required
- `run` string, uuid, required

## Query parameters

- `limit` integer
- `starting_after` string, uuid
- `ending_before` string, uuid

## Headers

- `Factuarea-Version` string, date

## Response `200`

- object: Cursor-paginated list envelope shared by every v1 listing endpoint. `data` holds the page items; `has_more` signals whether another page exists; `next_cursor` is the cursor to pass back for the following page. Treat it as strictly opaque: send it through verbatim, never parse it and never assume a format — it is not the same across listings, and each listing documents its own cursor parameter. Concrete listings narrow `data` to their resource type via `allOf`.
  - `data` object[], required: Items on the current page.
    - `id` string, uuid, required: Opaque identifier of the step, a UUID v7.
    - `object` 'automation_step_run', required: Always `automation_step_run`.
    - `step_index` integer, required: Zero-based position of the step inside its run, unique within the run. It is the identity of the step: it is what `automation_run.step_dead_lettered` publishes as `data.step_index`, and what `POST /v1/companies/{company}/automations/runs/{run}/steps/{step_index}/replay` takes.
    - `action_type` 'notify_in_app' | 'notify_channel' | 'emit_webhook' | 'create_calendar_event' | 'send_document_email' | 'send_payment_reminder' | 'change_status' | 'tag_entity' | 'create_task' | 'change_task_status' | 'assign_task' | 'add_task_comment', required: Type of action of the step, as frozen in the rule version snapshot.
    - `status` 'pending' | 'running' | 'succeeded' | 'failed' | 'skipped' | 'dead_lettered' | 'blocked', required: State of the step: `pending`, `running`, `succeeded`, `failed`, `skipped`, `dead_lettered` (parked awaiting a manual replay) or `blocked` (stopped by a guardrail).
    - `attempts` integer, required: Attempts the step has consumed.
    - `parameters` object, required: Parameters of the action, frozen from the rule version snapshot. `{}` when the action takes none.
    - `result` object, required: Trace the action adapter returned. `{}` while the step has not finished, or when the adapter produced no trace.
    - `discard_reason` string, nullable, required: Classified reason of the step outcome, or `null` when there is none. Returned VERBATIM as stored: the current engine writes values of a closed catalogue (`condition_not_matched`, `chain_depth_exceeded`, `step_attempts_exhausted`, …), but rows written before that catalogue existed still hold free-form text, so do not model this as a fixed enum. The catalogue value IS guaranteed on the `data.discard_reason` extra of `automation_run.step_dead_lettered`.
    - `discard_reason_label` string, nullable, required: Human-readable label of `discard_reason`, in Spanish. `null` when there is no reason, or when the stored value predates the closed catalogue and therefore cannot be interpreted — in that case `discard_reason` still carries the raw value. The label is always Spanish: it is part of the vocabulary of the automation engine and does not follow `Accept-Language`.
    - `replayable` boolean, required: Whether replaying this step can produce a different outcome. `true` only for reasons caused by an external state you can change (a disconnected channel, a client without an e-mail address, exhausted attempts): those park the step in `dead_lettered`, the only state that allows re-entry. `false` for reasons frozen into the rule snapshot (an unregistered action type, invalid parameters) and for guardrail cut-offs, where a replay would take exactly the same branch. Also `false` when the reason is unknown to the catalogue.
    - `last_error` string, nullable, required: Technical message of the last failure of the step, or `null`. Orthogonal axis to `discard_reason`: this one is the raw message, that one is the classification.
    - `next_retry_at` string, date-time, nullable, required: When the step is scheduled to be retried (ISO 8601), or `null` when no retry is pending.
    - `claimed_at` string, date-time, nullable, required: When a worker claimed the step (ISO 8601), or `null` while unclaimed.
    - `finished_at` string, date-time, nullable, required: When the step reached its outcome (ISO 8601), or `null` while it has not finished.
  - `has_more` boolean, required: Whether more pages are available after this one.
  - `next_cursor` string, nullable, required: Opaque cursor for the next page, or `null` when `has_more` is `false`. The example below shows the shape used by most listings; others return a different one, so pass the value back verbatim instead of validating it.

## Other responses

- `400`: `parameter_unknown` with `subcode` `retired_active_profile_header`: the retired `X-Active-Profile` header was sent; the company goes in the path. `parameter_invalid_format` on `Factuarea-Version`: the header is not a `YYYY-MM-DD` date. `unsupported_api_version`: the `Factuarea-Version` date is not a supported version.
- `401`: Missing or invalid API key.
- `403`: The request is authenticated but not allowed: the API key lacks the required scope (`insufficient_scope`), the plan does not include the public API or this feature (`addon_not_active`, `feature_not_available_in_plan`), or the module is not available with a test key (`module_not_available_in_sandbox`). `company_inactive`: the company in the path is deactivated; reactivate it to operate.
- `404`: The requested resource does not exist or belongs to another company. `profile_not_found`: the company in the path does not exist, is not in the API key scope or belongs to the other environment (live or test).
- `422`: Validation failed. The `error.param` field identifies which input is invalid.
- `429`: Rate limit exceeded. Retry after the duration in `Retry-After`.
- `500`: Unexpected server error.

---

[API](https://skmtc.dev/factuarea/apis/factuarea-public-api.md) · [All operations](https://skmtc.dev/factuarea/apis/factuarea-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/factuarea/apis/factuarea-public-api/revisions/718e77934422?raw)
