---
title: "getMonitoringRunCompletionV2"
method: POST
path: "/v2/integrations/{integrationId}/monitoring/runs/completion"
tags: ["monitoring", "integrations"]
---

# getMonitoringRunCompletionV2

`POST /v2/integrations/{integrationId}/monitoring/runs/completion`

## Request

- Base URL: `https://integration-toolkit.sls.epilot.io`
- URL: `https://integration-toolkit.sls.epilot.io/v2/integrations/{integrationId}/monitoring/runs/completion`
- Auth: HTTP bearer

Per-event completion of one or more runs (every monitoring event sharing a
`correlation_id`), each reduced to "how many events have reached a final
outcome, and how many are still going". Unlike `getMonitoringStatsV2`, which
counts rows, this groups `erp_monitoring_v2` by `event_id` first: an event
retried twice and then processed counts once, as a success.

Per event: it is **settled** once it has a `success` row, a terminal `error`
row (a non-retryable failure, retries exhausted, or the dead-letter closure),
or nothing but informational rows (deduplicated, skipped). A `warning` never
settles an event on its own: mapping and relation caveats are written BEFORE
the final stage's success row, and a retry marker (`warning` with
`will_retry: true`) means the queue will deliver it again. Everything else is
**in flight**. A settled event's outcome is its worst terminal row — `error`
beats `warning` beats `success`.

For an ERP file import, an event the mapping split into several writes (one row
writing a product and a price, say) settles only once every write has reported a
final result, whatever those results were; until then it is in flight. A write
lost without any result keeps its event in flight.

Batched so a history page costs one request: pass every run on the page. A run
that has reported nothing yet comes back with `seen: 0`, never omitted.

The denominator is NOT here: the producer owns it. For an ERP file import it is
`ErpImportProgress.events_published`; `seen` is how many of those have reported
anything yet. Freshness is near-real-time (ClickHouse ingestion lag).

## Path parameters

- `integrationId` string, uuid, required

## Request body

- GetMonitoringRunCompletionV2Request
  - `correlation_ids` string[], required: The runs to report on — an import's `import_id` is its `correlation_id`. At least one; a history page's worth (tens, not thousands) is the intended batch.

## Response `200`

Per-event completion, one entry per requested run.

- MonitoringRunCompletionV2Response
  - `runs` MonitoringRunCompletionV2[], required: One entry per requested `correlation_id`, in request order.
    - `correlation_id` string, required
    - `seen` integer, required: Distinct events that have reported at least one row under this run.
    - `in_flight` integer, required: Events that have reported something but no final outcome yet: only mapping or relation caveats so far (the success row comes last, from the final stage), or a failed attempt the queue will retry. An event stuck here with an old `last_activity_at` was most likely lost on its final attempt (processor crash) — a stall, not a slow run. Includes split events still waiting on some of their writes.
    - `retrying` integer, required: The subset of `in_flight` whose latest row is a retry marker — the queue will deliver them again after a temporary failure.
    - `success` integer, required: Settled events whose terminal rows were all successes.
    - `warning` integer, required: Settled events processed with a caveat (unique-id matched several entities, attribute type mismatch, …) and no terminal error. Retry markers are not caveats and are never counted here.
    - `error` integer, required: Settled events with at least one terminal error row (retries exhausted, or non-retryable).
    - `info` integer, required: Settled events whose rows were all informational — deduplicated or skipped, nothing to apply.
    - `entries` MonitoringRunEntriesV2, required: The run's monitoring rows counted by level — the rows its trace lists, correlation-level rows (wait outcomes, closes) included. Unlike the event counts above, one event can contribute many rows (one per entity written, plus every caveat and retry). Retry markers count under `retry` only, so a failed attempt that later succeeds is not an error; `total` is the sum of the others.
      - `total` integer, required
      - `error` integer, required: Error rows that will not be retried.
      - `warning` integer, required: Warning rows that are caveats, not retry markers.
      - `success` integer, required
      - `info` integer, required
      - `retry` integer, required: Retry markers (`will_retry` in `detail`) — attempts the queue delivers again.
    - `last_activity_at` string, date-time, nullable, required: Newest row of any level under this run; null when nothing has reported yet.

## Other responses

- `400`: Bad request. `message` says what is wrong. A request refused by the request validation, before any handler ran, also carries `status: 400` and the raw validation errors under `error`.
- `401`: Unauthorized: the request carries no bearer token. The API gateway answers `{"message": "Unauthorized"}`. A token that is present but invalid or expired is refused with 403, not 401.
- `403`: Forbidden: the token lacks a permission the operation requires, or it is invalid or expired — the API gateway refuses those with `{"message": "Forbidden"}` before the request reaches the API.
- `500`: Internal Server Error

## Changes

- **2026-10-14** `7500d8e50bd0`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/epilot/apis/integration-toolkit-api/changes/v2/integrations/:integrationId/monitoring/runs/completion/post.md)

---

[API](https://skmtc.dev/epilot/apis/integration-toolkit-api.md) - [All operations](https://skmtc.dev/epilot/apis/integration-toolkit-api/llms.txt) - [OpenAPI document](https://skmtc.dev/epilot/apis/integration-toolkit-api/revisions/7d07c4eb8d37?raw)
