MCP metric dictionary
Last updated: 2026-09-03
What every number the OuterSignal MCP tools return actually counts: the group it is computed over, whether it is before or after tax, how a date range changes it, and what a zero means. Each entry is derived from the query that produces the figure.
Which customers a number covers
Every field in the reference below names one of these groups. Two figures over different groups can both be correct and still disagree.
- Brand base
- Every customer record OuterSignal holds for the brand, whether or not they have ever ordered and whether or not they carry a persona. This is the denominator behind every brand total and behind the percentage fields on the persona tools.
- Leaves out: records deleted at the source, and records you deleted in OuterSignal; email addresses on your ignored-customers list.
- Persona base
- The subset of the brand base that currently carries a persona. Persona tools compute their per-persona rows over this population only. It is smaller than the brand base — often much smaller — and the difference is not reported as a bucket.
- Leaves out: everything the brand base excludes; customers not yet processed by persona tagging; customers processed but matched to no persona.
- Window buyers
- When you pass date_from or date_to, the population narrows to customers with at least one order inside the range. A customer with no order in the range drops out entirely — they are not counted at zero.
- Leaves out: everything the parent population excludes; customers whose orders all fall outside the range; customers with no orders at all.
- Customers with the attribute known
- Attributes sourced from enrichment — age, gender, location, industry, job title — are only known for the customers enrichment resolved. Averages and breakdowns over these attributes count only the customers where the value is present, so their denominator is smaller than the persona base and differs field by field.
- Leaves out: everything the parent population excludes; customers with the attribute missing or unresolved.
How revenue is measured
Three different measurements of money are in play, and which one you get depends on the tool and on whether you supplied a date range. They are not slices of one another.
- Merchandise revenue
- Summed from individual order lines: what the products cost, after per-line discounts. This is the basis for persona revenue whenever you supply a date range, and for the weekly revenue distribution.
- Includes: Product price times quantity; Per-line discounts subtracted.
- Excludes: Tax; Shipping; Order-level discount codes; Refunds — a refunded order still contributes; Cancellations — a cancelled order still contributes.
- Platform lifetime spend
- The lifetime total your store platform reports for each customer, carried through unchanged. Used for persona revenue and LTV whenever you supply no date range. It is a different measurement from merchandise revenue, not a wider or narrower slice of it — it can include orders placed before OuterSignal started receiving your data, and orders whose line detail never reached us.
- Includes: Tax and shipping, for most sources; Order history that predates your OuterSignal connection.
- Excludes: Nothing we subtract — the figure arrives already computed.
- Allocated order subtotal
- Every product figure. We take each order’s pre-tax subtotal and spread it across that order’s lines in proportion to their prices, so a product’s revenue reflects its share of what was actually charged. Spreading an order across its own lines never changes the order total, so this cannot move a brand-level figure up or down.
- Includes: Product subtotal after order-level discounts.
- Excludes: Tax; Shipping; Refunds — a refunded order still contributes; Orders we could not match to a customer; Lines with no product name.
One name, several meanings
These names appear in more than one place with more than one definition. If two OuterSignal answers disagree, check this list first.
- total_revenue
- get_persona_overview switches basis with your date range: without dates it reports platform lifetime spend, with dates it reports merchandise revenue inside the range. get_persona_revenue reports platform lifetime spend either way. Ask both tools the same question with the same dates and you will get two different, individually correct numbers.
- "brand", as a comparison base
- Three different populations answer to this word. get_persona_overview’s percentages divide by every customer, narrowed by your dates. get_persona_revenue’s benchmarks divide by every customer, all time, never narrowed. get_persona_radar and get_persona_products compare against persona-carrying customers only. Check which base you are reading before comparing two personas across tools.
- property_value_distribution
- get_persona_demographics uses five bands over everyone in the persona, so the bands add to well under 100%. get_persona_professional uses seven bands over customers with a known property value, so those add to 100%. The same persona will show a much higher percentage in the professional view. Both are right.
- Product revenue
- get_persona_products sums line prices. search_products and get_product_detail spread each order’s subtotal across its lines. For the same product these do not agree, and the difference is order-level discounts and how each handles shipping and tax.
- Buyer counts for a product
- search_products counts every buyer on the order lines. get_product_detail and compare_products count only buyers who have a live customer profile with us, so they agree with each other and come in at or below search_products.
- Zero and empty
- A zero can mean nobody matched, nobody has the attribute recorded, or a privacy floor withheld a small group. Where we can tell you which, we do — the age bands carry a suppressed flag, and the product tools carry a suppression tier. Elsewhere, read the population column above before treating a zero as a finding.
Field reference
Every tool, every field it returns, and what that field counts.
list_organizations
The brands this connection can reach.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| name / entity_slug / default | Each brand you can query, the identifier to pass as the organization argument, and which one is used when you pass none. | Not a measurement | Not money | None | A brand missing from this list is one this connection cannot currently reach. |
Easy to misread
- Access is re-checked on every call. A brand you have left disappears from this list immediately, with no token change involved.
list_active_personas
The personas that currently have at least one customer assigned.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| persona_id | A persona that at least one customer currently carries. | Persona base | Not money | None. This tool accepts date_from and date_to and then ignores them — the list is always all-time. | A persona missing from the list has no customers assigned to it right now. |
| persona_name / description | The persona name and description as configured in OuterSignal. | Not a measurement | Not money | None | A null description means none was configured. |
Easy to misread
- "Active" means the persona has customers assigned, not that those customers ordered recently. A customer with zero orders still keeps a persona active.
- No customer counts are returned. A persona with one customer looks identical to a persona with a hundred thousand.
get_persona_summary
One row per persona, plus brand-level totals.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| customer_count | How many customers currently carry this persona. | Persona base — narrowed to Window buyers when you pass dates | Not money | Yes, by changing who is counted: with dates, only customers who ordered inside the range. | A persona with no customers produces no row at all. |
| customer_percent | This persona's customers as a share of the brand base. | Numerator: Persona base. Denominator: Brand base | Not money | Yes, both sides narrow together | 0 can mean the denominator was unavailable rather than a genuine 0%. |
| total_revenue | Revenue attributed to this persona. | Persona base — Window buyers when you pass dates | Merchandise revenue with dates; Platform lifetime spend without dates | Yes — and the basis itself changes. A windowed total and an unwindowed total are different measurements and will not reconcile. | 0 can mean the figure was missing rather than genuinely zero. |
| revenue_percent | This persona's revenue as a share of brand revenue. | Numerator: Persona base. Denominator: Brand base | Same basis on both sides | Yes | 0 can mean the denominator was unavailable. |
| total_orders | Orders attributed to this persona. | Persona base — Window buyers when you pass dates | Not money | Yes. With dates it counts distinct orders inside the range; without dates it is the lifetime order count your platform reports. | 0 can mean the figure was missing rather than genuinely zero. |
| avg_aov | Revenue divided by orders, for this persona. | Persona base — Window buyers when you pass dates | Follows total_revenue | Yes, and the basis changes with it | 0 means there were no orders to divide by, not that the average order was worth nothing. |
| avg_age | Average age of this persona. | Customers with the attribute known | Not money | The cohort narrows; the age itself is not a windowed quantity | null means nobody in the persona has a known age. It is never reported as 0. |
| lifetime_avg_ltv | Average lifetime spend of the customers in this persona. Each customer contributes their whole history, not just the part inside your date range. | Persona base — Window buyers when you pass dates | Platform lifetime spend, in both branches | The value moves with the window because the window picks a different set of customers. Narrowing to a recent range selects more active buyers and can raise it substantially. The lifetime_ prefix describes each customer, not the group. | 0 can mean the figure was missing. |
| lifetime_repeat_rate | The share of customers in this persona with two or more orders in their lifetime. | Persona base — Window buyers when you pass dates | Not money | Same as lifetime_avg_ltv: the test is lifetime, the group is windowed, so the number moves with the window. | 0 means nobody in the group has two or more lifetime orders. |
| coverage.customers_with_persona / coverage.customers_total / coverage.scope | How many of the customers behind this response currently carry a persona, how many there are in all, and which of the two questions you are being answered. | Numerator: Persona base. Denominator: Brand base. Both narrow together, so the ratio is always like for like. | Not money | Yes, and the payload says so. scope reads all_time when you pass no dates — that is coverage of your customer base. It reads window when you pass either date, and then both figures count only customers who ordered inside your range, which is a different and usually smaller question. | Zero means no customer behind this response carries a persona. It gives you the size of the gap, not its cause: at all_time scope it means no assignments exist yet, and at window scope it can simply mean none of that window’s buyers happen to carry one. |
| brandTotals.totalCustomers / totalRevenue / totalOrders | Brand-wide totals, for context alongside the persona rows. | Brand base — narrowed to Window buyers when you pass dates | Same dual basis as total_revenue | Yes | 0 can mean the result set was empty. |
Easy to misread
- The persona rows do not sum to the brand totals, and they are not supposed to. Persona rows cover only customers who carry a persona; brand totals cover everyone. There is no "no persona" row accounting for the difference — see the questions section below.
- For the same reason, customer_percent and revenue_percent across all personas do not add up to 100%.
- Passing a date range changes what a dollar means, not just which dollars are counted. Do not compare a windowed revenue figure to an unwindowed one.
- Refunded and cancelled orders still contribute to windowed revenue.
- Persona labels are always the customer’s classification as of now. No history is kept, so no date range can answer "how did persona membership change over time".
- coverage narrows with your date range like everything else. Read as "how much of my customer base is tagged", ask with no date range; with one it answers "how many of this window’s buyers are tagged", which is a different and usually smaller question.
get_persona_comparison
The same measurements as get_persona_summary, projected side by side for comparing personas.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| customer_count / customer_percent / revenue_percent | Identical definitions to get_persona_summary. | Persona base — Window buyers when you pass dates | Same dual basis | Yes | As get_persona_summary |
| avg_aov / lifetime_avg_ltv / lifetime_repeat_rate | Identical definitions to get_persona_summary. | Persona base — Window buyers when you pass dates | Same dual basis | Yes | As get_persona_summary |
Easy to misread
- The percentages are shares of the whole brand, but this tool does not return the brand totals — you cannot reconstruct the denominator from this response alone. Call get_persona_summary if you need it.
- Revenue and order totals are computed but not returned here; use get_persona_summary for the absolute figures.
- This is the one persona tool that can filter by order tag. The filter works on whole orders: excluding a tag drops those orders from every figure here, so a persona whose orders all carry it can fall out entirely. Figures filtered this way will not match get_persona_summary, which has no such filter — that is the filter working, not a disagreement.
get_revenue_distribution
Revenue and orders per persona, per week.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| period_data[].period_start | The Monday of the week the revenue fell in. | Persona base | Not money | Yes | A missing week means no revenue that week. Empty weeks are not filled in with zeros. |
| period_data[].revenue | That persona's revenue in that week. | Persona base | Merchandise revenue, always — this tool has no unwindowed branch | Yes | 0 can mean the figure was missing. |
| period_data[].order_count | Distinct orders in that week for that persona. | Persona base | Not money | Yes | 0 can mean the figure was missing. |
| available_range.min / .max | The full span of order history available, ignoring your date range. | Persona base | Not money | No — deliberately all-time, so you can tell whether your range covers the data that exists. | Empty strings mean there is no order history to report. |
Easy to misread
- This tool returns absolute dollars, never shares. If you compute a persona’s share by dividing by the sum of the returned rows, the denominator is persona-carrying customers only, not brand revenue — every share computed that way is overstated.
- Passing only date_from, or only date_to, silently ignores it. You get the default 52-week window ending at your most recent order instead. Pass both or neither.
- The default window is anchored on your most recent order, not on today.
- Persona names are not attached to these rows — only numeric persona_id. Pair with list_active_personas.
export_persona_summary
get_persona_summary as a single structured export, optionally filtered.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| personas[] | The same per-persona measurements as get_persona_summary, minus total_orders and avg_age. | Persona base — Window buyers when you pass dates | Same dual basis | Yes | As get_persona_summary |
| brandTotals | Brand-wide totals. | Brand base — every customer, not only the personas you asked for. Narrowed to Window buyers when you pass dates, exactly as in get_persona_summary. | Same dual basis | Yes | As get_persona_summary |
| coverage.customers_with_persona / coverage.customers_total / coverage.scope | How many of the customers behind this response currently carry a persona, how many there are in all, and which of the two questions you are being answered. | Numerator: Persona base. Denominator: Brand base. Both narrow together, so the ratio is always like for like. | Not money | Yes, and the payload says so. scope reads all_time when you pass no dates — that is coverage of your customer base. It reads window when you pass either date, and then both figures count only customers who ordered inside your range, which is a different and usually smaller question. | Zero means no customer behind this response carries a persona. It gives you the size of the gap, not its cause: at all_time scope it means no assignments exist yet, and at window scope it can simply mean none of that window’s buyers happen to carry one. |
| personaCount | How many persona rows this response contains. | The rows returned, after your persona_ids filter | Not money | Yes. A persona whose customers placed no order inside your date range produces no row, so this count falls as you narrow the range. | 0 means every persona you asked for was unknown, currently has no customers, or had no buyer inside your date range — the three cases look the same. |
| exportedAt | When this response was generated. | Not a measurement | Not money | No | This is the time of your request. It is not a statement about how fresh the underlying data is. |
Easy to misread
- Filtering with persona_ids narrows the rows but not the percentage denominators. A single-persona export still reports that persona’s share of the whole brand, not 100%.
- Asking for a persona that exists but has no customers returns no row, which looks the same as asking for an id that does not exist.
get_persona_overview
One persona’s headline numbers.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| customer_count / customer_percent / total_revenue / revenue_percent / total_orders / avg_aov / avg_age / lifetime_avg_ltv / lifetime_repeat_rate | Exactly the same measurements get_persona_summary returns for this persona — same computation, same populations. | Persona base — Window buyers when you pass dates | Merchandise revenue with dates; Platform lifetime spend without dates | Yes | As get_persona_summary |
Easy to misread
- The percentages here are shares of your whole customer base, but this tool does not return the brand totals it divided by. Call get_persona_summary if you need the denominator.
- total_revenue here and total_revenue from get_persona_revenue are different measurements for the same persona over the same dates — see the "One name, several meanings" section.
get_persona_demographics
Age, gender, property value and education for one persona.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| age_distribution[].percent | Share of each age band. | Customers with the attribute known — specifically customers whose age we know and who are 18 or older. Not the persona. | Not money | The group narrows with your dates; age itself is a lifetime attribute | A band with suppressed set to true had too few people to report and was zeroed. A zero without that flag means genuinely nobody. |
| avg_age / median_age | Average and median age. | Customers with the attribute known — every customer with a known age, including under-18s, so a slightly wider group than the bands above | Not money | Group narrows with dates | null means nobody in the persona has a known age. |
| gender_distribution[].percent | Share of each gender, including an Unknown bucket. | Everyone in the persona. This is the one breakdown whose shares add to 100%, and its Unknown bucket is exactly the share we hold no gender for. | Not money | Group narrows with dates | A large Unknown bucket is an enrichment gap, not a data error. |
| property_value_distribution[].percent | Share of each home-value band. | Everyone in the persona. The bands therefore add up to well under 100%, and the missing remainder — customers we hold no property value for — has no row of its own. | Not money | Group narrows with dates | A low percentage across every band means low coverage, not that your customers own inexpensive homes. |
| avg_property_value / median_property_value | Average and median home value. | Everyone in the persona, counting customers with no known property value as zero | Not money | Group narrows with dates | These read low, and the median reads 0, whenever we hold a property value for fewer than half the persona. Read them as coverage multiplied by the real average, not as the real average. |
| education_level[].percent | Share holding each highest degree. | Customers with the attribute known — those whose first listed degree we could read | Not money | Group narrows with dates | A level missing from the list had nobody in it. Other means a degree was listed but not recognized. |
Easy to misread
- This response contains four breakdowns computed over four different groups. Gender is over everyone; age is over customers whose age we know; education is over customers whose degree we could read; property value is over everyone. Only gender adds to 100%, and only gender’s Unknown bucket shows you the coverage gap.
- Below 30 customers this tool returns an error rather than a result. That is a privacy floor.
get_persona_geographic
Where one persona’s customers are.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| countries[].percent | Share of the persona in each country. | Customers in the persona whose country we know. Only the United States and Australia are reported; customers in every other country are dropped from the list after the percentages were worked out. | Not money | Group narrows with dates; location is a lifetime attribute | A country missing from the list is not a country with no customers — it may simply not be one of the two we report. |
| states[].percent | Share of the persona in each state. | Customers in the persona whose state we know — a different group from the country percentages, so the two are not comparable | Not money | Group narrows with dates | Unknown means the state could not be resolved. |
| cities[].percent | Share of the persona in each city. | Customers in the persona whose city we know — a third distinct group | Not money | Group narrows with dates | Only the top cities are returned. |
| countries[].total_revenue | Revenue from the persona’s customers in that country. | Customers in the persona whose country we know | Platform lifetime spend | No — this stays lifetime even when you pass a date range. It is not revenue in your window. | 0 means no recorded spend. |
Easy to misread
- Country, state and city percentages each divide by their own known-location group. They do not add up to each other and none is a share of the whole persona.
- Only the United States and Australia are reported as countries. An international brand sees a partial map whose percentages still read like whole-brand shares.
- Customers with no country recorded are attributed to the persona’s dominant country. That is an assumption, not data.
- Below 30 customers this tool returns an error rather than a result.
get_persona_professional
Jobs, employers, education and property value for one persona.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| top_occupations[].percent | Share holding each job title. | Customers in the persona whose first listed job title we hold. Not the persona. | Not money | Group narrows with dates; employment is a lifetime attribute | An empty list means we hold no readable employment record for this persona’s customers — see the questions section for why that happens. |
| top_employers[].percent | Share working at each company. | Customers whose first listed employer we hold | Not money | Group narrows with dates | Empty means no readable employer records. |
| top_industries[].percent | Industry mix, inferred from job titles. | The ten most common job titles only — not the persona, and not everyone whose job we know. These shares add to 100% of that narrow base. | Not money | Group narrows with dates | When job titles are highly varied the top ten cover only a small slice, and this breakdown is correspondingly narrow. |
| top_institutions[].percent | Share who attended each institution. | Customers whose first listed institution we hold | Not money | Group narrows with dates | Empty means no readable education records. |
| property_value_distribution[].percent | Share in each home-value band. | Customers with a known, positive property value. These shares add to 100%. | Not money | Group narrows with dates | Empty means no known property values. |
Easy to misread
- Every list here is capped at ten entries with no total alongside it, so you cannot tell a top ten covering most of the persona from one covering a few percent.
- The property-value breakdown here uses different bands and a different group from the one in get_persona_demographics. Both are correct; they answer different questions.
- Only the first entry of a customer’s employment and education history is read. Someone whose current job sits second in their record contributes nothing.
- Below 30 customers this tool returns an error rather than a result.
get_persona_revenue
Spending behavior for one persona, against brand benchmarks.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| total_revenue / avg_ltv / median_ltv / avg_order_value / median_order_value / avg_order_count | Spending totals and averages for the persona. | Persona base — Window buyers when you pass dates | Platform lifetime spend, always | Your dates change who is in the group but never which dollars are counted — every figure stays a lifetime figure. This is the opposite of get_persona_overview, where dates switch the revenue basis. | 0 means no spend or no orders. |
| revenue_percent | The persona’s revenue as a share of the brand. | Numerator: the persona, narrowed by your dates. Denominator: every customer, all time, never narrowed. | Platform lifetime spend | Numerator only. A narrow date range drives this toward zero for reasons unrelated to the persona. | 0 means no brand revenue to divide by. |
| brand_avg_aov / brand_avg_ltv | Brand-wide benchmarks to compare the persona against. | Every customer, all time | Platform lifetime spend | Never — these do not respond to your date range | 0 means no brand data. |
| repeat_rate | Share of the persona with two or more lifetime orders. | Persona base — Window buyers when you pass dates | Not money | Group narrows; the test stays lifetime | 0 means nobody reordered. |
| avg_days_between_orders / first_purchase_to_repeat | The average gap between orders, over customers with at least two orders and both dates recorded, capped at 365 days. | Repeat buyers in the persona | Not money | Group narrows with dates | These two fields are the same number under two names. Neither is specifically the gap between a first and second order. |
| spending_tiers[].percent | Share of the persona in each lifetime-spend band. | Everyone in the persona — these add to 100% | Platform lifetime spend | Group narrows with dates | 0 means nobody in that band. |
Easy to misread
- The brand benchmarks and revenue_percent here never respond to your date range, while the persona figures beside them do. Comparing a windowed persona to an all-time brand understates the persona.
- This tool’s money is always lifetime spend. get_persona_overview’s money switches to in-window merchandise revenue when you pass dates. Same persona, same dates, two different revenue numbers — both correct under their own definitions.
get_persona_radar
Five composite scores placing one persona against your brand: affluence, velocity, loyalty, growth, influence.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| affluence / velocity / loyalty / growth / influence | Each score is a weighted blend of a few underlying measures, rescaled so that 50 means "exactly your brand average". Every 1% above the brand average adds one point, so 60 means roughly 10% above average and 40 roughly 10% below. | The comparison base is your persona-carrying customers, not all your customers. Individual inputs are narrower still: the affluence score’s property component covers only customers with a known property value, and the influence score’s follower component only customers with a social profile. | Platform lifetime spend, where money is involved | Yes — both the persona and its comparison base narrow together | A score of exactly 50 has more than one cause: genuinely average, or no data to compute from. Treat a flat set of 50s as "no signal" rather than "perfectly average". |
Easy to misread
- The scale saturates quickly. A persona at 1.5 times the brand average scores 100, and so does one at 5 times — the top and bottom of the scale cannot be told apart. Use these to rank personas, not to size a difference.
- Unlike the demographic tools, this one has no minimum-customer floor. A persona with a handful of customers still returns five confident-looking scores.
- The comparison base is persona-carrying customers only, which is a different base from the percentages in get_persona_overview and the benchmarks in get_persona_revenue.
get_persona_products
What one persona buys, against what the brand buys.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| product_affinity[].brand_revenue / brand_units | Revenue and units for each of your ten biggest products. | Persona base — deliberately, so the comparison is like for like. This is smaller than the same product’s revenue in search_products, which counts every buyer. | Merchandise revenue | Yes | 0 means no sales in scope. |
| product_affinity[].persona_revenue / persona_units | This persona’s revenue and units for the same products. | Persona base, this persona only | Merchandise revenue | Yes | 0 means the persona bought none of it. |
| product_affinity[].affinity_index | How much more or less of a product this persona buys than its overall share of spend would predict. 100 means exactly in line. | Persona base | Merchandise revenue | Yes | 0 means the persona did not buy the product at all — it is not a very low affinity, it is no purchases. |
| top_products[].revenue / units_sold / avg_price | The persona’s biggest products. avg_price is the plain average of the line prices, not revenue divided by units. | Persona base, this persona only | Merchandise revenue | Yes | 0 means no sales in scope. |
| distinct_product_count / avg_products_per_order | How many different products the persona bought, and the average basket size. | Persona base, this persona only | Not money | Yes | 0 means no orders in scope. |
Easy to misread
- product_affinity always covers your ten biggest products brand-wide, never the persona’s own top ten. A persona whose favourite product is your fortieth best seller will not see it here — use top_products for that.
- Revenue here is measured differently from search_products and get_product_detail, so the same product will not carry the same revenue in both places. This tool sums line prices; the product tools spread each order’s subtotal across its lines.
- This tool returns no buyer counts, so it cannot tell you how many of a persona bought a given product.
- channel_id is only applied when you also supply a date range. Passing a channel with no dates returns brand-wide numbers.
search_products
Browse or search the catalog, one row per product title.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| products[].revenue | The product's revenue. | Everyone who bought it, taken straight off the order lines — no requirement that we hold a customer profile for them | Allocated order subtotal | Yes. With no dates, all time. | Products with no revenue are dropped from the results entirely rather than listed at zero. |
| products[].buyerCount | How many distinct customers bought it. | Same as revenue — every buyer on the order lines | Not money | Yes | Products with no buyers are dropped from the results. |
| products[].aov / unitsSold | Revenue divided by the orders containing this product, and total units sold. | Same as revenue | aov: Allocated order subtotal | Yes | 0 means there were no orders to divide by. |
| products[].enrichedBuyerCount | How many of this product’s buyers we hold demographic detail for (an age or a gender). | Customers with the attribute known — a narrower population than buyerCount, because it also requires a live customer profile | Not money | Yes | 0 means we hold demographic detail for none of them, not that nobody bought it. |
| products[].exclusivityRate / crossBuyRate | The share of this product’s buyers who bought nothing else, and the average number of other products its buyers also bought. | This product's buyers | Not money | Yes | 0 means no buyers to measure. |
| products[].newBuyerRate | The share of this product’s buyers whose first-ever purchase from you was recent. | This product's buyers | Not money | Partly. "First ever" looks at the customer’s whole history, and with no date range "recent" means the last 365 days. | 0 means none of them were new. |
| products[].revenueTrend / buyerTrend | Change against the immediately preceding period of the same length. | This product's buyers | Allocated order subtotal | Yes | null means the previous period was too thin to compare against. It does not mean no change. |
| products[].personaDistribution[] | How this product’s buyers split across personas. | This product’s buyers who currently carry a persona | Not money | Yes | The shares add up to the share of buyers who carry a persona, not to 100%. An empty list means too few buyers to report without exposing individuals. |
| kpis.totalProducts / totalRevenue / totalUniqueBuyers / avgAov | Whole-catalog totals shown alongside the results. | The entire catalog. These deliberately ignore your search term, and unlike the product rows they include shipping protection, gift cards, warranties and refund lines. | Allocated order subtotal | Yes | 0 means nothing in the window. |
| kpis.avgEnrichmentRate | The share of the buyers in scope for this query that the signal feed holds an age or a gender for, as a percentage from 0 to 100. | Unique buyers with a line item inside the requested window, product-type filter, and audience filter — the same population totalUniqueBuyers counts, from the same scan. The numerator is a subset of that by construction, so the value cannot exceed 100. | Not money | Yes, on the same window as the rest of the kpis block | A genuine 0 means we hold neither an age nor a gender for any buyer in scope. It is age-or-gender specifically, not every kind of enrichment we do — a brand rich in job titles but thin on demographics reads lower here than it might expect. |
Easy to misread
- The kpis block is not a summary of your search. Searching for one product still returns whole-catalog totals, computed without the non-product filtering that the product rows get. Do not read kpis.totalRevenue as "revenue for the product I searched for".
- Product rows exclude non-product lines — shipping protection, gift cards, warranties, tips, donations, refund and exchange lines, and anything on your hidden-products list. The kpis block does not.
- avgEnrichmentRate is a percentage (0-100), not a fraction. It is the same definition the per-product enrichment rate has always used, so the app and the connector agree.
- A product with zero revenue in the window is absent from the results, which looks the same as a product that does not exist.
- Searching matches any title containing your text, so several products can come back. Only the exactly-matching row is comparable with get_product_detail.
get_product_detail
Full analytics for one product, matched on its exact title.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| totalRevenue | The product's revenue. | Buyers who have a live customer profile in OuterSignal. This is narrower than search_products, which counts every buyer on the order lines. | Allocated order subtotal | Yes | 0 means no matching revenue. |
| totalBuyers | Distinct customers who bought it. | Buyers who have a live customer profile | Not money | Yes | 0 means nobody matched. |
| aov | Revenue divided by the orders containing this product. | Buyers who have a live customer profile | Allocated order subtotal | Yes | 0 means there were no orders to divide by. |
| enrichedBuyers / enrichmentRate | How many of this product’s buyers we hold demographic detail for, and that as a share of totalBuyers. | Customers with the attribute known | Not money | Yes | 0% means we hold demographic detail for none of them. |
| suppressionTier | Whether there were enough enriched buyers to report demographics without exposing individuals. | Not a measurement | Not money | Yes | When this reads suppressed, the demographic, professional and index sections come back null — that is a privacy floor, not missing data. |
| firstOrderAt | When this product was first ordered. | All orders of the product | Not money | No — always all time, so it does not move when you change the date range. | null means we have no order for it. |
| purchaseBehavior.repeatRate | The share of this product’s buyers who ordered more than once. | Buyers who have a live customer profile | Not money | Yes | 0 means nobody reordered. |
| purchaseBehavior.avgPurchaseFrequency | The average number of orders per buyer containing this product. It is a count, not a rate per year. | Buyers who have a live customer profile | Not money | Yes | 0 means no orders. |
| purchaseBehavior.monthlyRevenue[] | Revenue by month. | Buyers who have a live customer profile | Allocated order subtotal | No — always the last 12 months, regardless of the date range you passed. | An empty list means no revenue in the last 12 months. |
| purchaseBehavior.coBoughtProducts[] | Products frequently bought in the same order, with how often that happens and a lift score for how much more often than chance. | Orders containing this product | Not money | Yes | An empty list means no co-purchases met the floor. |
| demographics / professional / social / index values | How this product’s buyers differ from your other customers on each attribute. | Customers with the attribute known — compared against everyone else who is not a buyer of this product | Not money | Yes | null means the privacy floor suppressed the section, not that the attribute is absent. |
Easy to misread
- This tool counts only buyers we hold a customer profile for. search_products counts every buyer on the order lines. For the same product in the same window, this tool’s revenue and buyer count will be the same or lower, and the difference is profiles we have not built, merged away, or deleted — not lost sales.
- Unlike search_products, this tool does not strip non-product lines, so shipping protection or warranty revenue attached to the same title is included here.
- The title must match exactly. Use search_products to find the exact title first.
- monthlyRevenue and firstOrderAt ignore your date range by design.
compare_products
Two or three products side by side on who buys them.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| products[].buyerCount / enrichedCount | Distinct buyers of each product, and how many of those we hold demographic detail for. | Buyers who have a live customer profile — the same population as get_product_detail, so these two tools agree with each other | Not money | Yes | 0 means nobody matched. |
| dimensions[] | Each attribute where the products’ buyers differ, with the size of the difference and how confident we are it is real. | Customers with the attribute known, per product | Not money | Yes | A dimension is dropped when any one of the compared products lacks it, so a missing dimension does not mean the products are alike on it. |
| overlaps[].overlapPercent | How much two products’ buyer sets overlap: the shoppers who bought both, as a share of everyone who bought either. It is symmetric — swapping the two products gives the same number. | Buyers who have a live customer profile | Not money | Yes | 0 means no shared buyers. |
Easy to misread
- This tool returns no revenue at all — it is about who buys, not how much they spend. Use search_products or get_product_detail for money.
- overlapPercent is not "the share of A’s buyers who also bought B". It is the share of everyone who bought either product that bought both, so it is lower than the one-directional figure you may be expecting.
get_product_next_purchases
What customers tend to buy after a given product.
| Field | What it counts | Over which customers | Revenue basis | With a date range | If it is zero or empty |
|---|---|---|---|---|---|
| transitions / sequence counts | How often buyers of the anchor product went on to buy each other product next. | Buyers who have a live customer profile | Not money | Yes | Results below the minimum buyer floors are withheld rather than shown as small numbers, so an empty result means "not enough evidence", not "never happens". |
Easy to misread
- Sequence results have minimum buyer floors. A quiet product returns nothing rather than an unreliable answer.
Common questions
- Why do only some of my customers have a persona?
- Because assigning a persona depends on research completing for that customer first, and then on their profile actually matching one of your personas. A customer can be waiting for research, can have been researched and matched nothing, or can have been researched with too thin a profile to place. get_persona_summary and export_persona_summary return a coverage block giving the two numbers directly — how many of the customers behind the response carry a persona, and how many there are in all — so the gap is a figure you can read rather than something to infer from a small percentage. The block also carries a scope field so you can tell which question you were answered: all_time means coverage of your customer base, window means coverage among the customers who ordered inside the date range you gave, which is a smaller question. Ask with no date range if you want the first one. A low or zero figure tells you the size of the gap, not its cause — zero without a date range means no assignments exist yet, which is usually tagging not being switched on but can also be tagging having run and matched nobody. Ask us which if it matters.
- Why is the professional data empty for my personas?
- Job titles, employers and education come only from third-party research. Unlike location, they have no fallback to anything in your order data, so a customer we have not researched — or one research came back empty on — contributes nothing. We also read only the first entry of each customer’s employment and education history, so a record whose current role sits second contributes nothing either. This is a different cause from low persona coverage: every customer in a professional breakdown already carries a persona, and so has already been through research. Empty professional results mean research found no usable employment record for them, not that tagging is incomplete.
- What does "enrichment coverage" mean, and how is it measured?
- More than one thing, which is why two coverage figures in OuterSignal can disagree without either being wrong. Persona coverage is the share of your customers who carry a persona. Demographic coverage — what the product tools report as enriched buyers — is the share for whom we hold an age or a gender. Those measure different things over different groups. The dimension breakdowns add a third layer: each one divides by the customers who have that particular attribute, so age coverage, education coverage and property-value coverage all differ from one another within a single response. The population column in every table above names which group each figure is computed over.
- What exactly is "my customer base"? Different tools give different totals.
- Two different measurements, both correct. The persona tools’ brand totals come from what your store platform reports for each customer: their lifetime spend and lifetime order count, copied across unchanged. That includes orders placed before OuterSignal started receiving your data, orders whose line detail never reached us, and — depending on your platform — tax and shipping. The product catalog totals are built up from the individual order lines we received and matched to a customer, counting the pre-tax product subtotal only. So the brand total is normally the larger of the two on both revenue and orders, while the customer counts agree, because both resolve the same set of people. A gap between them is a difference in what is being measured, not missing data on either side. One thing it is definitely not: how we split a multi-item order across its products. That split always adds back up to the order total, so it cannot change a brand figure at all.
- How are the index and radar scores computed?
- The five radar scores compare a persona against your own brand, not against other merchants and not against a fixed scale. 50 means exactly your brand average, and each point above or below is roughly one percent. The comparison base is your persona-carrying customers. The scale saturates: a persona at one and a half times your average scores 100, and so does one at five times, so use the scores to rank personas rather than to size the difference between them. Product index values work on the same idea — a product’s buyers compared against your other customers on the same attribute — with the comparison drawn only from customers for whom that attribute is known.
- Are revenue figures before or after tax, and is that consistent?
- It is not consistent, and the differences are structural rather than accidental. Three bases are in play. Product figures spread each order’s pre-tax subtotal across its lines. Persona figures with a date range sum line prices net of per-line discounts, which excludes tax, shipping and order-level discount codes. Persona figures without a date range report your platform’s own lifetime spend per customer, which for most platforms includes tax and shipping. Refunded and cancelled orders still contribute everywhere. Every table above names which basis its figures use, and adding a date range to a persona question changes the basis rather than simply narrowing it — so an all-time query and a wide date-range query are not the same question.
- Do personas reflect what a customer was at the time they ordered?
- No. A persona is always the customer’s classification right now. We keep no history of persona membership, so a date range restricts which orders are counted, never which persona a customer counted as at the time. A query over last year’s revenue attributes it to this year’s personas. Period-over-period comparisons of persona membership are not something these tools can answer.