Commerce Lifecycle States
Consume and update the retention lifecycle state of each commerce — dual identifiers, freshness, and behavior with unresolved data.
Lifecycle states are the current snapshot of each commerce's retention state, computed by the Reten engine. Each state answers the question: "where in the retention cycle is this commerce, under which configuration, and since when?".
This section documents the read and write contract for those states.
Capabilities
The API exposes five capabilities over the commerce-lifecycle-snapshots resource:
List (paginated + filters)
GET /commerce-lifecycle-snapshots — walks every state in the tenant with
filters by code, stage, AGC and freshness.
Look up by commerce
GET /commerce-lifecycle-snapshots/:commerceIdentifier — the state (or
states) of a single commerce by UUID or external code.
Batch lookup
POST /commerce-lifecycle-snapshots/batch-lookup — resolves up to 100
identifiers in a single call, with partial results.
Single upsert
POST /commerce-lifecycle-snapshots — creates or updates the state of a
commerce.
Batch upsert
POST /commerce-lifecycle-snapshots/batch-upsert — writes up to 1000 states
in a single operation, with per-item reporting.
Authentication
Every lifecycle state endpoint accepts two authentication methods:
| Method | Headers | Typical use |
|---|---|---|
| API Key | x-api-key: YOUR_API_KEY | External integrators / partners. The key resolves the tenant automatically — do not send x-tenant-id. |
| JWT | Authorization: Bearer <token> + x-tenant-id: <tenant-id> | Internal use / admin panel. |
Required permissions:
| Permission | Capabilities |
|---|---|
commerce-lifecycle-snapshot:view | list, look up by commerce, batch lookup |
commerce-lifecycle-snapshot:create | single upsert, batch upsert |
activity-generation-config:view | discover the available AGCs (see AGC Setup) |
The examples in this section use the API Key method (x-api-key), which is the expected path for an external integrator. If you consume from the internal panel with a JWT token, replace that header with Authorization: Bearer <token> + x-tenant-id: <tenant-id>.
The resource: state snapshot
A lifecycle state (snapshot) is represented with this canonical shape. The surfaces that return the "raw" resource (list, batch lookup, upsert) use exactly these fields:
{
"id": "5d000000-0000-4000-8000-000000000001",
"commerce_id": "c0000000-0000-4000-8000-000000000001",
"external_id": "SV-001",
"activity_generation_config_id": "a1b2c3d4-0000-4000-8000-000000000001",
"code": "AT_RISK_MONTHLY",
"label": "At risk (monthly)",
"stage": "ENGAGEMENT",
"last_computed_at": "2026-06-10T06:00:00.000Z",
"state_changed_at": "2026-05-22T06:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier of the snapshot. |
commerce_id | UUID | UUID of the commerce in Reten. |
external_id | string | null | External code of the commerce in your system (e.g. "SV-001"). Denormalized at upsert time; null if the commerce has no external code. |
activity_generation_config_id | UUID | AGC under which this state was computed. See AGC identifier. |
code | string | Raw retention state code (e.g. "AT_RISK_MONTHLY"). Corresponds to a UserRetentionStatusConfig of the tenant. |
label | string | Human-readable label of the state, derived (denormalized) from the code config. The config is the source of truth. |
stage | enum | Lifecycle stage. See stages. |
last_computed_at | ISO 8601 | When the engine last computed this snapshot. See freshness. |
state_changed_at | ISO 8601 | When the visible state (stage, code) last changed. See freshness. |
Identifier model (dual)
A commerce can be referenced in two equivalent ways across the entire states API:
- Reten UUID (
commerce_id) — the internal identifier, e.g.c0000000-0000-4000-8000-000000000001. - External code (
external_id) — the commerce's code in your own system, e.g."SV-001".
Each endpoint declares which one(s) it accepts:
| Endpoint | UUID | External code | Rule |
|---|---|---|---|
Look up by commerce (GET /:commerceIdentifier) | ✅ | ✅ | The path accepts either of the two, interchangeably. |
| Batch lookup | ✅ (commerce_ids) | ✅ (external_ids) | A single type per request (no mixing). |
| Batch upsert | ✅ (commerce_id) | ✅ (external_id) | Homogeneous: every item in the batch uses the same type. |
| Single upsert | ✅ (commerce_id) | ❌ | UUID only. |
Using the external code spares you from maintaining a mapping of Reten UUIDs in your system: you write and read using the same codes you already handle internally.
Lifecycle stages (stage)
The stage field takes one of three possible values:
ACTIVATIONENGAGEMENTRESURRECTION
The (stage, code) pair defines a commerce's visible state.
Freshness: last_computed_at vs state_changed_at
These are two timestamps with distinct semantics; understanding the difference is key to consuming this data correctly.
| Field | What it answers | Who controls it |
|---|---|---|
last_computed_at | "When was this state last recomputed?" | Provided by the caller (the engine) on every upsert. |
state_changed_at | "Since when has the commerce been in this visible state (stage, code)?" | Managed by Reten automatically. |
state_changed_at rules:
- On create, it is initialized equal to
last_computed_at. - On update, it only changes if the
(stage, code)pair differs from the one that already existed. If only thelabelor thelast_computed_atchanges but(stage, code)stays the same,state_changed_atdoes not move.
Example of the evolution of a single commerce:
| Event | stage / code | last_computed_at | resulting state_changed_at |
|---|---|---|---|
| Computation 1 (creation) | ENGAGEMENT / AT_RISK_MONTHLY | 2026-05-22T06:00Z | 2026-05-22T06:00Z (initialized) |
| Computation 2 (same state) | ENGAGEMENT / AT_RISK_MONTHLY | 2026-06-01T06:00Z | 2026-05-22T06:00Z (unchanged) |
| Computation 3 (stage changed) | RESURRECTION / WIN_BACK | 2026-06-10T06:00Z | 2026-06-10T06:00Z (updated) |
Use last_computed_at to answer "how recent is this data?" and
state_changed_at for "how long has the commerce been in this state?" (e.g.
"it's been at risk for 19 days").
Behavior with unresolved or stale data
Unresolved
An identifier is unresolved when no snapshot exists for it. The API groups two cases together (intentionally — the result is unobtainable either way):
- The commerce does not exist in the tenant.
- The commerce exists but does not yet have a computed state.
How it manifests per endpoint:
| Endpoint | Behavior with an unresolved identifier |
|---|---|
| Batch lookup | The identifier appears in the unresolved array. The request does not fail. |
| Look up by commerce | Responds 404 Not Found. |
| List | Simply doesn't appear in data (the list only returns states that exist). |
Stale (old data)
The API does not flag freshness or delete old states: a snapshot lives until the engine overwrites it. "Freshness" is decided by the consumer, comparing last_computed_at against the current moment and applying its own threshold (e.g. "I consider anything computed more than 24h ago stale").
To find recent states without walking everything, list accepts the state_changed_since filter, which returns only states whose state_changed_at is after a given date.
AGC identifier and setup flow
The lifecycle state is per (commerce, AGC) pair, not unique per commerce. An "Activity Generation Config" (AGC) is the configuration under which the engine computes states; the same commerce can have a different state under each AGC.
That's why the activity_generation_config_id parameter appears in every endpoint — read and write:
- It is optional on reads. If you omit it, you get the states across all AGCs of the commerce/tenant.
- It matters if you want to avoid "mixing" data. Filtering by a specific AGC ensures you read only the states computed under that configuration, without intermingling results from other AGCs.
- It is required on writes (single and batch upsert), because a state is always written under a specific AGC.
How to obtain the available AGC IDs
If you want to filter by AGC, first discover the ones that exist in your tenant with:
GET /api/activity-generation-configsAuth: requires the activity-generation-config:view permission. Responds with an array; take the id of the AGC you care about and use it as activity_generation_config_id:
[
{
"id": "a1b2c3d4-0000-4000-8000-000000000001",
"name": "B2B Retention",
"description": "Retention cycle for wholesale customers"
},
{
"id": "a1b2c3d4-0000-4000-8000-000000000002",
"name": "Q1 Resurrection",
"description": "First-quarter win-back campaign"
}
]If your tenant operates with a single AGC, you can omit
activity_generation_config_id on reads and work without it. The parameter
becomes important when multiple AGCs coexist and you don't want to mix their
states.