# Analytics API reference

Read overview, audience, campaign, offer, and flow metrics with their exact filters and response contracts.

Available on the Scale and Pro plans.

Analytics endpoints read recorded activity. They do not preview draft performance or publish configuration. Different reports use different units and date boundaries, so their totals are not interchangeable.

## Authentication and shared query fields

Paths below follow `https://api.rightmessage.com/v1/{teamPid}`. All five operations are GET and accept `read`, `write`, or `*`. The [shared request conventions](https://rightmessage.com/help/rest-api-overview#base-url-and-authentication) cover bearer authentication, headers, project access, and rate limits.

Required query fields `start` and `end` must be valid dates. Optional `timezone` defaults to `UTC`. Only the campaigns endpoint validates the timezone identifier; the other endpoints accept it as a string.

Use query arrays such as `offer_ids[]=ofr_example1` and `flow_ids[]=ofn_example1`, not comma-separated strings. There is no pagination parameter in this family.

The illustrative examples use fictional WorkForge IDs and counts.

## GET /analytics/overview

Returns project-wide answer, enrichment, offer, question, and campaign metrics.

Accepts only the shared query fields. Requests spanning more than 30 days use a start of end minus 30 days. The returned interval is `day`; the endpoint does not accept an interval override.

The ordinary overview queries use inclusive timestamp bounds. A date-only end parses to midnight, not the end of that day. The timezone argument controls time-series buckets; it does not reinterpret an otherwise timezone-less input timestamp. Campaign arm totals separately use local calendar dates, so their boundary semantics differ. Supply explicit timestamp offsets when you need exact bounds.

Returns 200 with the following `data` fields:

| Field                                                                                          | Shape                                                                                      |
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `interval`                                                                                     | String, day                                                                                |
| `exposures`, `conversions`                                                                     | Arrays of `{holdback:boolean,result:number}` for personalized and holdback campaign totals |
| `totalAnswers`, `totalRespondents`, `totalNewEnrichedPoints`, `totalNewEnriches`               | Numbers                                                                                    |
| `answersData`, `respondentsData`, `oldAnswersData`, `newEnrichedPointsData`, `newEnrichesData` | Arrays of `{x:timestamp,y:number}`                                                         |
| `totalVisitorOffersConverted`, `totalVisitorQuestionsConverted`                                | Numbers                                                                                    |
| `offersData`                                                                                   | Exposure/conversion totals, series, and offer breakdowns below                             |
| `questionsData`                                                                                | Exposure/conversion totals, series, and question breakdowns below                          |
| `toBeUsedForNewEnrichesData`                                                                   | Object containing `syncUpsData` below                                                      |

Both `offersData` and `questionsData` have numeric `visitorsExposed`, numeric `visitorsConverted`, and `timeSeries` entries with `x`, `visitorsExposed`, and `visitorsConverted`. `offersData.offerBreakdowns[]` adds `offerId` to that same structure. `questionsData.questionBreakdowns[]` adds `questionId`.

`syncUpsData` contains `subscribersEnriched: {value:number}`, `dataPointsEnriched: {value:number}`, and `timeSeries[]` entries with `key`, `subscribersEnriched`, and `dataPointsEnriched`.

Campaign arm totals sum across campaigns that have testing enabled. They are not globally deduplicated visitor totals. Campaign-total failure can yield empty arm arrays while other overview data remains available. Daily unique values must not be added to reconstruct range-wide unique counts. The retained `oldAnswersData` field currently repeats the respondent series.

Overview query failures are not always surfaced as errors. Caught batch failures and missing aggregation results can produce a 200 response with zero metrics and empty series. An all-zero overview alone cannot distinguish no activity from an unavailable reporting backend.

```http
GET /v1/123456/analytics/overview?start=2026-10-01&end=2026-10-13&timezone=UTC HTTP/1.1
Host: api.rightmessage.com
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
```

Illustrative response excerpt; the field table defines the complete contract:

```json
{"data":{"interval":"day","totalAnswers":40,"totalRespondents":25,"answersData":[{"x":"2026-10-01T00:00:00.000Z","y":4}],"exposures":[{"holdback":false,"result":200},{"holdback":true,"result":20}],"conversions":[{"holdback":false,"result":10},{"holdback":true,"result":1}]}}
```

## GET /analytics/audience

Returns unique-visitor counts for each segment in the requested period.

Accepts the shared query fields. The time range uses inclusive start and exclusive end. Returns 200 with `data.segmentCounts`, a map from segment ID to unique-visitor count. The string key `null` represents missing segment membership. The query returns up to 10,000 segment buckets. Counts use cardinality aggregation and can be estimates.

The audience query does not apply the supplied timezone to its timestamp bounds. Use explicit timestamp offsets, or UTC dates with the exclusive end set to the following day. This endpoint does not support a `goalId` filter.

```http
GET /v1/123456/analytics/audience?start=2026-10-01&end=2026-10-14 HTTP/1.1
Host: api.rightmessage.com
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
```

```json
{"data":{"segmentCounts":{"seg_example1":25,"null":10}}}
```

## GET /analytics/campaigns

Returns personalized and holdback performance for published campaigns.

Adds optional string `campaign_id`. Unknown IDs filter to an empty result rather than producing an item 404. Timezone must be valid. Dates normalize to local calendar dates, both inclusive. End must not precede start, and the inclusive range must not exceed 90 days. Invalid ranges return 422 under `errors.end`, including `invalid_range` or `range_too_long`.

Returns 200 with `data` keyed by campaign ID. An empty result can serialize as `[]`.

| Campaign field        | Meaning                                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------- |
| `exposures`           | Personalized-arm visitors                                                                |
| `pageviews`           | Personalized-arm engagement pageviews                                                    |
| `conversions`         | Personalized-arm outcome converters                                                      |
| `conversionRate`      | Percentage, rounded to one decimal; zero when no visitors                                |
| `variants`            | Array of variant objects below                                                           |
| `aggregateLift`       | Observed percentage lift rounded to one decimal, or null                                 |
| `aggregateStatistics` | Null or `zScore`, `pValue`, integer `confidenceLevel`, and boolean `isSignificant`       |
| `experiment`          | Null or the independent decision's `state`, `label`, `startedAt`, and `decidedAt` fields |

A variant object contains nullable `variantId`, string-array `segmentIds`, numeric `traffic`, numeric `conversions`, and percentage `conversionRate` rounded to two decimals. The holdback row has null variant ID and an empty segment list. Variant counts can overlap when visitors match multiple variants.

Aggregate statistics require a measurable outcome, enabled testing, visitors in both arms, and holdback converters. `isSignificant` is the range p-value check, not the experiment's durable Winner decision. Campaigns come from live configuration. Visitors observed in both arms are excluded from the campaign reporting cohorts.

```http
GET /v1/123456/analytics/campaigns?start=2026-10-01&end=2026-10-13&campaign_id=cpn_example1 HTTP/1.1
Host: api.rightmessage.com
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
```

```json
{"data":{"cpn_example1":{"exposures":200,"pageviews":260,"conversions":10,"conversionRate":5,"variants":[],"aggregateLift":null,"aggregateStatistics":null,"experiment":null}}}
```

Unavailable team, combination, or engagement results return 503 with `{"error":"Failed to fetch campaign performance."}`.

## GET /analytics/offers

Returns offer conversion metrics and a comparison with the previous period.

Adds optional `offer_ids`, an array of strings with no declared item-count limit. A nonempty list enables offer-level splitting. The time range uses inclusive start and exclusive end; the timezone controls histogram buckets, not the raw query bounds.

Supply explicit offer IDs. When the list is omitted or empty, the query does not include normal offer events. The returned `overall` series is therefore not a reliable all-offer total without explicit IDs.

Returns 200 with a `data` array. Each item contains:

- `for`, an array identifying the aggregation key, such as `["overall"]` or an offer key.
- Numeric `numerator`, unique converters, and `denominator`, unique exposed visitors.
- `data`, a daily array of `{x,numerator,denominator}`.
- Nullable numeric `change`, relative rate change as a fraction, not percentage points.
- `previousPeriod` with `numerator`, `denominator`, and fractional `rate`.

The comparison period is the immediately preceding interval of equal duration. With no prior exposure, change is null. If the prior rate is zero and the current rate is positive, change is 1. Current rate is calculated from numerator and denominator; there is no top-level rate field in each item.

Caught batch-query failures can also return a 200 with empty aggregation data rather than an explicit reporting error. Do not interpret an empty offers response as confirmed zero conversions.

```http
GET /v1/123456/analytics/offers?start=2026-10-01&end=2026-10-14&offer_ids[]=ofr_example1 HTTP/1.1
Host: api.rightmessage.com
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
```

```json
{"data":[{"for":["ofr_example1"],"numerator":10,"denominator":200,"data":[],"change":null,"previousPeriod":{"numerator":0,"denominator":0,"rate":0}}]}
```

## GET /analytics/flows

Returns flow executions, node metrics, and the pages where flows appeared.

Adds optional string `flow_id` and optional `flow_ids`, an array of at most 25 strings. A nonempty `flow_ids` array takes precedence over `flow_id`. Without either filter, the service queries all flows. A date-only end expands through the end of that local day. An explicit timestamp is used as supplied.

Single-flow and unfiltered responses place one metrics object under `data`. Multi-flow responses use `{"data":{"flows":{"FLOW_ID":METRICS}}}`. Single-flow and multi-flow results are cached for 300 seconds, with question attribution sensitive to configuration changes. Unfiltered results are not cached this way.

The metrics object contains `flowMetrics`, `nodeMetrics`, and `exposurePages`. It adds `estimates: {questions:true}` when question counts use the large-volume estimate path.

| Nested object          | Fields                                                                                                                                                               |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `flowMetrics`          | Numeric `totalExecutions`, `completedRuns`, `unfinishedRuns`, `uniqueVisitors`, `identifiedVisitors`, and `avgDurationMs`; `completionBreakdown` map; `byFlow` array |
| `flowMetrics.byFlow[]` | `flowId`, `executions`, `uniqueVisitors`, percentage `completionRate`                                                                                                |
| `nodeMetrics`          | Map keyed by node ID; each value has `nodeId`, `nodeType`, `visitorCount`, and `avgDurationMs`                                                                       |
| `exposurePages[]`      | `url`, `pathname`, `hostname`, `uniqueVisitors`, and `executions`                                                                                                    |

Node metrics can add `conversionCount` and percentage `conversionRate`, plus type-specific count maps `splitDistribution`, `answerDistribution`, `branchDistribution`, `offerDistribution`, and `bucketDistribution`. Question metrics can add boolean `estimated` and `coverage`, with `status` of available or unavailable, nullable `reason`, and nullable `from` timestamp. Unavailable coverage sets question visitor and conversion values to null, not zero. Exact question attribution switches to estimates at 40,000 visitors. Question coverage uses a 90-day retention boundary.

Question-event attribution uses the current draft-first configuration, even though the events are recorded traffic. If the same question occurs more than once in a flow, the service omits question-event attribution for those repeated nodes rather than assigning events to an arbitrary occurrence. Traversal metrics can still be present.

```http
GET /v1/123456/analytics/flows?start=2026-10-01&end=2026-10-13&flow_id=ofn_example1 HTTP/1.1
Host: api.rightmessage.com
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
```

```json
{"data":{"flowMetrics":{"totalExecutions":20,"completedRuns":12,"unfinishedRuns":8,"uniqueVisitors":18,"identifiedVisitors":5,"avgDurationMs":12000,"completionBreakdown":{"completed":12},"byFlow":[{"flowId":"ofn_example1","executions":20,"uniqueVisitors":18,"completionRate":60}]},"nodeMetrics":{},"exposurePages":[{"url":"https://example.com/","pathname":"/","hostname":"example.com","uniqueVisitors":18,"executions":20}]}}
```

A metrics-query failure returns 503 with `{"status":"unavailable"}`. Flow filters do not check whether the flow exists, so an unknown flow ID does not have the same resource-404 contract as the flows CRUD API.

## Common errors and interpretation

Authentication, access, validation, and throttling use the [shared error contract](https://rightmessage.com/help/rest-api-overview#error-contract). Invalid dates or filter types return 422. Other reporting exceptions can produce 500. Null, empty, unavailable, and zero values have different meanings. No endpoint on this page changes the draft or starts an experiment.
