Analytics API reference
Read overview, audience, campaign, offer, and flow metrics with their exact filters and response contracts.
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 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.
Illustrative response excerpt; the field table defines the complete contract:
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.
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.
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, anddenominator, unique exposed visitors. data, a daily array of{x,numerator,denominator}.- Nullable numeric
change, relative rate change as a fraction, not percentage points. previousPeriodwithnumerator,denominator, and fractionalrate.
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.
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.
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. 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.