FullVision
Entities

Visitors

gethttps://data.fullvision.io/visitors

This endpoint describes your whole audience in one call: how much traffic the site got, which devices, browsers and countries it came from, which hours of the week people show up in, and how many of each month's newcomers came back.

Use it when you have a traffic number and want to know who is behind it — are these the same people as last month, what are they browsing on, where are they — before you drill into any single channel, page or keyword.

The question it answers: how big is my audience, what is it made of, and does it come back?

One form only — there is no single visitor

Every other entity endpoint has a single form you reach by passing an id. This one does not: there is no single visitor and no addressing key, so there is nothing to look up and nothing to hunt for. Every call returns the whole-audience view, narrowed only by date and by host.

What comes back

Eight blocks. Six of them follow page_url_scope; the last two describe the whole workspace and ignore it.

BlockWhat it isWhen you'd read it
trafficSite-wide totals over the range: page views, visitors, sessions, bounce rate, and the split between people (human_*) and automated traffic (agent_*), plus ai_user_page_views — the slice of that automated traffic where a real person's AI assistant opened the page for them. One row per host.You want the top-line number for the period, or you want to know how much of your "traffic" is machines rather than people.
devicesThe split of your audience by device class.You are deciding whether a mobile layout problem is worth fixing.
browsersThe split by browser.A bug report says "it breaks in Safari" and you want to know how many people that is.
countriesThe split by country.You are choosing which language or currency to add next.
active_hoursA day-of-week × hour heatmap of when people are on the site.You are picking a send time for a campaign or a window for a deploy.
retentionA cohort matrix by first-visit month: each cohort's starting_visitors plus raw return counts m0…m12 (divide by starting_visitors for a rate).You want to know whether people come back at all, not just whether new ones arrive.
new_vs_returningFirst-time versus repeat visitors. Workspace-wide — it carries scoped: false.Your traffic jumped and you want to know if it is new people or the same people visiting more.
engagementThe workspace engagement top-line. Workspace-wide — it carries scoped: false.You want one summary number for how engaged the audience is.

new_vs_returning and engagement say scoped: false in the payload precisely so you do not read them as host-specific: page_url_scope does not narrow them.

traffic visitor counts are upper bounds, not distinct counts. page_views and sessions are exact sums over the range, but unique_visitors, human_unique_visitors and agent_unique_visitors are computed by adding up each day's distinct count — a visitor who returns on 5 days counts 5 times. They are exact only for a single-day range. Measured over 30 days on a live marketing host: 25,682 reported vs 22,241 true distinct (+15%), and +44% on a busier product host. Report them as "visits by unique visitors (upper bound)", never as a true visitor total.

traffic is an array, one row per host, each carrying host_scope. The same double-count applies across those rows, so never sum the visitor columns across hosts either. page_url_scope defaults to marketing, so you normally get exactly one row; the multi-row shape shows up when you pass page_url_scope=all, or when the workspace has no URL configured for the scope you asked for.

retention fans out the same way: one row per (cohort_period, host_scope), so cohort_period alone is not a unique key unless a scope filter is pushed — key by both, or you will silently overwrite one host's cohort with another's. starting_visitors and m0…m12 are distinct-visitor counts, so never sum them across hosts either. Rows arrive newest cohort first, then host_scope ascending; pass page_url_scope for one row per month.

retention's date range picks cohorts by birth month, not by activity — a zero is usually "not yet", not churn.

FullVision keys the matrix on cohort_period, so range / from + to decide which cohorts exist in the response. This endpoint defaults to range=last_30_days, so the default call returns only the current month's cohort, and its m1…m12 are all 0 simply because those months have not happened yet. Reading that as 0% month-1 retention is wrong.

Two rules follow:

  • Pass a range of 12 months or more (range=last_year) before drawing any retention conclusion. Verified on live data: range=last_30_days returns one cohort with an all-zero tail, while an unbounded call returns cohorts back to May with real m1 / m2 values.
  • Only trust m{i} for cohorts that are actually i months old. For every younger cohort that cell is structurally zero.

A window that straddles a month boundary also excludes the previous month's cohort, even when that cohort's later-month activity falls inside the window.

This endpoint describes an audience; it does not build one you can act on. For a list of individual people, use People; to save a reusable segment, use Audiences.

Parameters

Four, and that is the whole surface: this endpoint declares no addressing key and no endpoint-specific extras.

ParameterWhat it doesWhen you'd use it
page_url_scopePins the report to one host — marketing (your website) or product (your app). Narrows traffic, retention, devices, browsers, countries and active hours. Defaults to marketing. all removes the pin: traffic then returns one row per host and retention one row per (cohort_period, host_scope) — never sum the visitor columns across those rows (see the warning above).You want your marketing site's audience without logged-in app traffic mixed into it; pass product to look at the app instead, or all to compare every host you have side by side.
range / from + toSets the date window. range is a preset; from + to are explicit dates and are ignored when range is set.You are comparing this month to last. Note that for retention this window selects which cohorts exist — see the warning above.
granularitydaily, weekly or monthly. Adds a series array of date-bucketed totals on top of everything you already get; it never changes the other blocks.You want to chart traffic over time instead of reading one number for the range.
fieldsComma-separated list of the blocks you want back. Omit it for all eight.The full response is ~51 KB and your chart needs one split — see below.

Trimming the response — fields

fields is a comma-separated list of response blocks. Omit it and you get every block — it only ever narrows a response, never widens one, so every existing call keeps its exact shape.

This is the endpoint fields was designed for: the untrimmed response is ~51 KB and active_hours + countries alone are 88% of it. Here one block is one query, so every block you drop is a query the server skips.

BlockResponse key
traffictraffic
devicesdevices
browsersbrowsers
countriescountries
active_hoursactive_hours
new_vs_returningnew_vs_returning
engagementengagement
retentionretention
?fields=traffic                    → one query, one key back
?fields=devices,browsers           → the two splits, ~2 KB instead of ~51 KB
?fields=devices&granularity=daily  → devices plus the date series

Rules that bite:

  • One form only. There is no single visitor, so there is no second block list to resolve against.
  • Row identity is never droppable. host_scope and range survive every trim.
  • Empty, duplicated, unknown, and * are all 400s — fields=, fields=devices,devices, fields=nope, fields=*. The error names the legal blocks.
  • granularity is independent — series is not a block. ?fields=devices&granularity=daily still returns the series, because the series answers granularity, not fields. Its unique_visitors is the same add-up-the-days upper bound at anything coarser than daily.
  • fields never lowers the scopes the endpoint requires. web:read is needed for any block.

Authorization

AuthorizationBearer <token>

API key as Bearer token

In: header

Query Parameters

page_url_scope?string

Which host to scope to. Defaults to marketing. all removes the host predicate entirely (every host in the workspace).

Value in

  • "marketing"
  • "product"
  • "all"
range?string

Preset date range. Canonical names (last_30_days, last_7_days, last_3_months, last_6_months, last_year, all_time, …) and dashboard short keys (30d, 7d, 3m, 6m, 1y, all) are both accepted. Mutually exclusive with from/to.

Value in

  • "last_7_days"
  • "last_14_days"
  • "last_28_days"
  • "last_30_days"
  • "last_90_days"
  • "current_month"
  • "this_month"
  • "last_3_months"
  • "last_6_months"
  • "last_year"
  • "all_time"
  • "7d"
  • "14d"
  • "28d"
  • "30d"
  • "90d"
  • "3m"
  • "6m"
  • "1y"
  • "all"
from?string

Custom start date (YYYY-MM-DD). Ignored if range is set.

Match^\d{4}-\d{2}-\d{2}$
to?string

Custom end date (YYYY-MM-DD). Ignored if range is set.

Match^\d{4}-\d{2}-\d{2}$
fields?string

Comma-separated response blocks to return. OMIT for every block of the resolved form (the default — this param only ever narrows a response, never widens one). The legal set is resolved AFTER form selection, so it depends on whether an addressing key was supplied: some blocks exist only on the single form, and a report whose resolved form declares none rejects fields with 400. An unknown or duplicated name is a 400 listing the legal blocks for that form.

granularity?string

Group form only. When set, the response gains a series array: scope-level totals per date bucket at this grain, for charting. Purely additive — rows is unchanged, and omitting this returns exactly the previous response. Ignored when an addressing key selects the single form.

Value in

  • "daily"
  • "weekly"
  • "monthly"
series_by?string

Group form only, requires granularity: split the series by one dimension. The response's series then holds one point per (bucket × segment), each carrying key. Accepted values: page-report → page; channel-report → channel (concrete source, the grain of sources[]), channel_category (the 9 buckets, the grain of rows[]); keyword-report → query; form-report → form_id; ad-report → campaign; email-report → campaign. An unrecognised value returns an empty series. Under a channel-report split, visitor counts are emitted as attributed_visitors (attribution cohort); unique_visitors on split points is a deprecated alias of that same value until 2026-11-26, not the host-scoped traffic count rows carry.

Responses

Audience descriptive statistics

get/visitors
curl -X GET "https://data.fullvision.io/visitors?page_url_scope=marketing&range=last_30_days"
Example Responses
{  "host_scope": "string",  "range": {    "from": "string",    "to": "string"  },  "devices": [    {}  ],  "browsers": [    {}  ],  "countries": [    {}  ],  "active_hours": [    {}  ],  "traffic": [    {}  ],  "retention": [    {}  ],  "new_vs_returning": {    "scoped": false,    "rows": [      {}    ]  },  "engagement": {    "scoped": false,    "rows": [      {}    ]  },  "series": [    {      "date": "string",      "property1": "string",      "property2": "string"    }  ],  "is_cached": true,  "age_s": 0,  "last_refresh": "string"}
application/json