GET /api/v1/campaigns
Purpose
GET /api/v1/campaigns returns the paginated campaign list used by the
operations page, plus list-level summary counters and status buckets.
The response has four top-level blocks:
campaignssummarystatus_countsapplied_filters
Query parameters
| Query param | Meaning | Notes |
|---|---|---|
tenant_id | Optional tenant scope | Applied where tenant data is available |
search | Free-text search | Matches title, description, scope, campaign_origin, and tags |
status | UI lifecycle filter | Compared against ui_status, not against the collapsed status field |
benefit | Benefit pillar filter | One of Conversion, Monetization, Retention |
type | Campaign type filter | One of General, Seasonal, Sports, Promo, Acquisition; CRM bonus rows match any associated lifecycle type |
channel | Notification channel filter | One of Push, SMS, Mail, InApp |
page | Page number | Defaults to 1 |
page_size | Page size | Defaults to 20, max 200 |
Data sources
The list endpoint does not have its own bespoke read model. It reuses the same merged campaign record pipeline as the overview endpoint:
- CRM
lifecycle_pools - CRM
content_pools - CRM
bonuses - CRM
content - CRM
reward_log - CRM
actions_log - Notify Triggers
flows_v2 - Notify Triggers
flow_versions_v2 - Notify Triggers
flow_runs_v2 - Notify Triggers
flow_working_copies_v2 - Notify Triggers
flow_release_attempts_v2 - Spark Bonus
dispatches - Lumio
conversationsfor thesummary.support_cases_totalfield
How filtering works
Base campaign set
The repository loads every CRM-backed and trigger-backed campaign record for the tenant scope.
Base filters
Before status is considered, the endpoint applies:
tenant_idsearchbenefittypechannel
This filtered set is used to compute status_counts.
Status filter
If status is provided, it is applied against ui_status, not against the
response field status.
This matters because:
ui_statuscan besuggestionstatusonly returnsrunning,paused, orended- rows with
ui_status = suggestionreturnstatus = paused
So status=suggestion can return rows whose response status field is
paused.
Top-level response blocks
| Block | Where it comes from | Important caveat |
|---|---|---|
campaigns | Paginated campaign rows after all filters | Sorted by ui_status, then end date, then start date, then title |
summary | Overview counters rebuilt from the filtered campaign rows | support_cases_total is not filtered by search, status, benefit, type, or channel |
status_counts | Counts by ui_status on the pre-status-filtered set | Buckets include suggestions, running, paused, ended |
applied_filters | Echo of normalized query values from the service layer | page and page_size are normalized defaults/max values |
Campaign row field map
| Field | How it is filled | Source or derivation |
|---|---|---|
id | Stable synthetic ID | crm_bonus:<bonus_id> for CRM rows, crm_reactive:<flow_id> for trigger rows |
bonus_id | CRM bonus identity | Present only for bonus-centric CRM rows |
tenant_id | Tenant string when known | CRM client_id; trigger-side only when Spark Bonus dispatches point to exactly one client_id |
title | User-facing campaign name | Exact CRM bonus title; trigger flow name |
status | Collapsed lifecycle status | running and ended stay as-is; everything else collapses to paused |
type | Compatibility campaign type | One associated type, or General when a CRM bonus spans several lifecycle types |
types | All campaign types | Additive CRM metadata derived from every lifecycle association |
benefits | UI benefit pillars | Hardcoded to Conversion, Monetization, Retention |
scope | Compatibility audience label | One associated scope, or Multiple audiences when a CRM bonus spans several scopes |
scopes | All audience labels | Additive CRM metadata derived from every lifecycle association |
channels | Possible delivery channels | CRM channels_templates; trigger notification action refs |
tags | Search-friendly synthetic tags | CRM bonus rows include all associated types and scopes; trigger rows use type, benefit, scope, and origin |
image_url | Campaign image when available | CRM content placeholders; currently empty for trigger rows |
benefit_scores | UI benefit score block | Hardcoded to 12, 6, 2 |
start_at | Campaign start time | Exact CRM bonus campaign start; trigger earliest successful notification execution |
end_at | Campaign end time | Exact CRM bonus campaign end; trigger latest successful notification execution |
campaign_origin | Upstream source label | crm or crm_reactive |
ui_status | UI-facing lifecycle bucket | suggestion, running, paused, or ended |
availability_status | Date-based bonus availability | Additive CRM value: active, future, ended, or unknown |
route_coverage | Lifecycle readiness summary | Additive CRM {ready,total,state} object; state is none, partial, or full |
CRM rows are grouped by bonus ID. A bonus referenced by several lifecycle content pools appears once, while its lifecycle types, scopes, channels, readiness, and activity are aggregated into that row.
Status derivation
CRM rows
CRM ui_status is inferred once per unique bonus from:
- whether any lifecycle association is ready
start_atend_at
The endpoint uses the Any-active plus coverage rule:
suggestionwhenready = falseendedwhen at least one route is ready butend_atis in the pastpausedwhen at least one route is ready butstart_atis in the futurerunningwhen at least one route is ready and the bonus is inside its date window, or when its dates are unspecified
Recent reward or action activity contributes metrics but does not downgrade a usable bonus from running. route_coverage independently reports how many associations are ready and whether coverage is none, partial, or full.
Trigger rows
Trigger ui_status is inferred from:
enabled- whether successful notification actions were seen
- whether successful Spark Bonus activations were seen
- whether the working copy is newer than the last successful release
Current logic:
runningwhen the flow is enabledsuggestionwhen the flow is disabled and has no sent or activated activitypausedwhen the working copy is newer than the last successful releaseendedotherwise
Summary block caveat
summary.support_cases_total is counted from Lumio chat conversations using
tenant scope only. It does not respect:
searchstatusbenefittypechannel
That field is therefore best read as "tenant-level Lumio chat volume alongside this list view", not "support cases caused by the filtered campaigns".
Truth gaps to keep in mind
benefitsandbenefit_scoresare currently UI placeholders, not measured campaign outcomes.statusis a simplified field. Useui_statuswhen the UI needs the real suggestion bucket.- CRM
typeandscoperemain singular compatibility fields. Use the additivetypesandscopesarrays when all associated values are needed.