---
title: "List bulk AP migrations"
method: GET
path: "/v1/stats/cohort-moves"
tags: ["Participants"]
---

# List bulk AP migrations

`GET /v1/stats/cohort-moves`

## Request

- Base URL: `https://api.peppolstatus.com`
- URL: `https://api.peppolstatus.com/v1/stats/cohort-moves`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Detected bulk migrations between Access Points, largest first. A cohort is a gap-≤3-day island of (from_provider, to_provider) mover days that clears three thresholds: at least 25 participants, at least 40 % of them on the busiest day (which rejects a steady drip), and at most 20 active days for the pair over the trailing 40 days (which rejects a recurring partnership).

Every day is the PROBE-OBSERVATION day — the day the change scan saw the SMP record change, not the day the migration was executed — so a cohort is always a `[first_day, last_day]` range and `peak_day` is the busiest observation day. Render the range, never a single date.

`top_country` is derived from the ICD prefix of the participant identifiers, not from business-card country fields. `merge_suspect` marks a cohort large enough (or whose source provider no longer resolves in the directory) to be a provider merge or a renamed provider rather than that many independent customer decisions — the canonical case is Sovos → Sage, 10,574 participants. Such rows are data events, not customer decisions; verify one before quoting it. The flag is a CURRENT judgment, re-evaluated on every recompute, not frozen at detection.

PLATFORM MOVES. A mover is a platform move when the platform that serves the participant stayed and only the certificate Seat changed: it is not a customer decision. The verdict is per DIRECTED provider pair (from → to), over all its movers since 2026-08-02, with one of two reasons (`same_host`, `sub_provider`): at least 90% of the movers whose endpoint host can be compared (5 or more, and at least 50% of all movers of the pair) kept their endpoint host, or at least 90% of 25 or more movers still carry, at the new provider, the sub-provider brand of the provider they left. It is a CURRENT judgment, computed again on every rollup cycle. A cohort of such a pair has `platform_move: true`. It stays in the list. A Vendor's cohorts (`provider=v-<slug>`) are not changed and have no `platform_move`.

`from`/`to` are OVERLAP bounds (a cohort counts when its range intersects the window), defaulting to the trailing 90 days the detector re-clusters. Mover history begins 2026-07-24, so no cohort predates it. `id` is a request-lifetime handle for the participant drill-down — never persist one.

## Query parameters

- `provider` string
- `direction` 'in' | 'out' | 'both'
- `from` string, date
- `to` string, date
- `min_participants` integer
- `merge_suspect` 'include' | 'exclude' | 'only'
- `limit` integer
- `cursor` string

## Response `200`

The detected bulk migrations, largest first.

- CohortMovePage: Detected bulk AP migrations, largest first.
  - `items` CohortMove[], required
    - `id` string, required: Request-lifetime handle for the participant drill-down, derived from the provider pair and first_day. Never persist it — a boundary shift changes it.
    - `first_day` string, date, required: Inclusive first probe-observation day of the burst.
    - `last_day` string, date, required: Inclusive last probe-observation day of the burst.
    - `peak_day` string, date, required: The busiest observation day inside the range.
    - `from_provider_key` string, required: The `/v1/aps/{key}` handle of the Provider the participants left.
    - `from_provider_name` string, required
    - `to_provider_key` string, required: The `/v1/aps/{key}` handle of the Provider they moved to.
    - `to_provider_name` string, required
    - `participants` integer, required: Participants observed moving across the whole range.
    - `peak_participants` integer, required: Participants observed moving on `peak_day` alone.
    - `pct_on_peak_day` number, required: The burst score: peak_participants / participants as a percentage, one decimal. The detector's floor is 40 %; a lower value would be a drip.
    - `active_days` integer, required: Distinct observation days inside this cohort.
    - `pair_active_days` integer, required: Distinct days this provider PAIR moved anyone over the 40 days ending at this cohort's last_day (the window is anchored to the cohort, not to today). A high value means a recurring flow rather than a one-off burst.
    - `top_country` string, nullable, required: Dominant country, derived from the ICD prefix of the participant identifiers (never from business-card fields); null when no ICD resolves.
    - `top_country_pct` number, nullable, required: Share of the cohort in `top_country`, percent with one decimal.
    - `sub_provider` string, nullable, required: Dominant curated reseller / sub-provider slug behind the moved participants; null when the cohort maps to none.
    - `sub_provider_pct` number, nullable, required: Share of the cohort behind `sub_provider`, percent with one decimal.
    - `sub_provider_name` string, nullable: The curated Vendor label of `sub_provider`; null when it is null.
    - `kind` 'provider' | 'vendor': `vendor` for a cohort of a Vendor's movers (`?provider=v-<slug>`): one side of the pair is then the Vendor. Absent means a cohort between two Providers.
    - `platform_move` boolean: True when the provider pair of this cohort (from → to) is a platform move: the platform stayed and only the certificate Seat changed, so the cohort is not that many customer decisions. A CURRENT judgment, re-evaluated on every rollup cycle. Absent on a Vendor's cohort (`kind: vendor`).
    - `merge_suspect` boolean, required: True when this looks like a provider merge or a renamed provider rather than that many independent customer decisions (≥ 5,000 participants, or the source provider no longer resolves in the directory). A CURRENT judgment, re-evaluated on every recompute — verify such a row before quoting it.
  - `next_cursor` string, nullable, required: Opaque cursor for the next page, or null on the last page.

## Other responses

- `400`: An invalid `direction`, `merge_suspect`, `from`, `to`, `min_participants` or `limit`.

---

[API](https://skmtc.dev/peppolstatus/apis/peppolstatus-api.md) · [All operations](https://skmtc.dev/peppolstatus/apis/peppolstatus-api/llms.txt) · [OpenAPI document](https://skmtc.dev/peppolstatus/apis/peppolstatus-api/revisions/7e74fb32f23c?raw)
