Get traffic geography
Returns where the store’s visitors were for a named reporting period.
The payload carries the resolved period window, the days of it geography could answer, the country breakdown ranked by visitors (capped at ten with a trailing “(other)” row folding the tail), the nested city breakdown, and the visitor totals the shares divide by.
Unlike the channel split on traffic/sources, the country breakdown is a partition: each visitor resolves to exactly one country, and visitors with no placed visit in the window are reported as unplaced_visitors rather than dropped, so the parts provably sum to the whole. Shares are not renormalised — their gap is the unplaced traffic.
Authorizations
Bearer token authentication
Query Parameters
Named reporting period (default last_30_days). Supported names: today, last_7_days, last_30_days, last_90_days, last_12_months, this_month, last_month. An unsupported name returns 422.
Response
Success
Store-traffic geography for a period — the resolved window, the days of it geography could answer, the ranked country breakdown with the revenue delivered to each country, and a nested city breakdown carrying its own capture window, wrapped in the standard API response envelope.
The days of the requested period that geography could answer — the requested window intersected with the days this store has carried placed visits. Every count in the payload is taken over this window rather than the requested one.
Null when the two do not meet, which means the period lies entirely before this store began carrying placed visits. Null arrives with an empty countries list and zeroed totals, and is deliberately distinct from an answerable window whose counts are all zero — that means nobody came. Do not coerce one into the other.
The days the revenue figures cover — the whole requested period, on the order's own clock. An order falls inside it by created_at, over the half-open UTC range [starts_on 00:00, (ends_on + 1) 00:00), the same predicate every conversion figure on this tab applies.
Never null, unlike effective_window, and it covers a different and longer span. Orders have carried a delivery country for years, while visit geography only reaches capture_start and can never be backfilled — an IP that was never recorded cannot be recovered. So on a period reaching back past capture, a country row shows revenue for the whole window beside visitors for only part of it. Neither number is wrong; each is right over its own span. Do not render the two columns as one measurement — say which days each covers, rather than leaving a reader to infer the gap from a suspiciously low visitor count. The gap closes as capture accrues and never inverts: revenue always covers at least as much of the window as visitors do.
The day this store first carried a placed visit (YYYY-MM-DD, UTC), or null when it never has. Reported whether or not the requested period overlaps it, so a client can say when geography began rather than presenting an empty period as an absence of traffic.
Countries ranked by resolved visitors, capped at ten and optionally ending in a single "(other)" row folding the tail.
This is a partition, not an attribution split: each visitor resolves to exactly one country — the country of their most recent placed visit in the window — so the rows are disjoint and Σ(visitors) + unplaced_visitors == total_visitors holds exactly.
A row's visitors and revenue answer two different questions and are not a numerator and a denominator: visitors counts people an IP placed in this country, revenue sums orders whose delivery address is here. Never divide one by the other into a conversion rate — the two populations are not the same people and the quotient means nothing. They also cover different spans; see revenue_window.
The city breakdown, nested rather than listed beside the countries because it answers a different set of days. City capture began after country capture, so this object carries its own capture_start, its own effective_window, and its own total_visitors.
cities.total_visitors will usually differ from the top-level total_visitors, because each counts the visitors of its own window. Both are correct. Divide city shares by cities.total_visitors and never by the top-level one.
Total distinct bot-free visitors over the effective window, placed and unplaced together. The denominator every share divides by.
Visitors with no placed visit in the effective window — counted, not dropped, so the country breakdown provably sums to the whole.
Shares are against total_visitors and are deliberately not renormalised, so Σ(shares) is the placed fraction rather than 1. The gap is this number, and it is meant to stay visible.
The revenue the country rows add up to over revenue_window, as a decimal string in the platform base currency — so a merchant totting the column up arrives at this number. The folded "(other)" row is included, exactly as it is in the column.
Every purchased order's revenue over the same revenue_window, whatever country it was delivered to or whether it named one at all, as a decimal string in the platform base currency — the whole of the population the country column is cut from.
Paired with accounted_revenue this is what makes the rows auditable: two checkable numbers rather than one computed percentage, so the client owns the phrasing. The identity is exact — accounted_revenue plus the revenue of purchased orders carrying no delivery country equals paid_revenue — and the two do not usually diverge, so a gap between them is worth looking into rather than the ordinary state of things.
Read it as the population's total, not as store revenue. It covers storefront purchases and excludes imported orders, which are a material share of platform revenue, so neither this number nor the column beneath it is a merchant's whole book.
The HTTP status code echoed in the response envelope.