FullVision
Entities

People

gethttps://data.fullvision.io/people

This endpoint returns the individual people behind your numbers — one person you name by identifier, or a filtered list of them you can page through.

Use it when a report gave you a count and you now need the actual humans in it: to open one customer's record, or to pull the list you are going to email, sync or hand to sales.

The question it answers: who are these people, how did they arrive, and what have they paid me?

Two forms: a list, or one person

q is the switch, and it is the only one.

  • Omit q and you get a list — person rows, newest first, paged with a cursor. This is the list-building primitive: it is not a ranked top-N, so you page through all of it rather than reading the first page as "the top".
  • Pass q and you get one person — their row, an aggregated lifetime-revenue summary, and (for paid-acquired customers only) the campaign that acquired them. FullVision rejects an identifier matching several people rather than guessing; re-address with the exact person_id.

What comes back

BlockWhat it isWhen you'd read it
Person row (both forms)Who this person is and how they arrived: email, your own user id for them (external_id), Stripe customer id, visitor id, first-touch channel and source, created_via and origin_detail, paid, current_plan, plus a journey_id pointer.You are building a list, or you want the one-line answer to "where did this customer come from?"
revenue (single form)The person's lifetime revenue rolled up from their payments: lifetime_revenue_cents, charge_count, first_charge_at, last_charge_at. Payments only — subscription starts, cancellations and refunds are excluded, so the count is charges, not events.You want to know what one customer is actually worth before you decide how to treat them.
acquisition (single form)The campaign that acquired them — present only for customers acquired through paid advertising, null otherwise.You are checking whether a specific customer came from an ad you paid for.

Rows carry personal data: the person's email address, the id your own system uses for them (external_id), and their Stripe customer id. /people, Journeys and Form Report's submissions block are the only surfaces that return personal data — everything else returns aggregates. /people requires the customer:read scope, and fields never lowers it.

People never returns the event timeline. A person row tells you who someone is, never what they did. For that, follow the row's journey_id to Journeys — the only surface that returns timelines.

People vs. form submissions. /people answers who did this form acquire — pass ?created_via=form&origin_detail=<form_id> for the person rows. Form Report's submissions block answers what did people submit: form_email, form_phone and form_name are the captured payload and exist only there, never on a person row.

Addressing

ParameterWhat it doesWhen you'd use it
qLooks up exactly one person by an identifier: email address, cus_… Stripe customer id, person or visitor UUID, or the user id from your own system. Omit it for the list form.You have an email from a support ticket, or a Stripe customer id from a webhook, and you want that person's record and lifetime revenue.

Filtering the list

Every filter below matches on the person's first touch or current state, not on a date range of activity.

ParameterWhat it doesWhen you'd use it
created_viastripe, form or events — which kind of record first identified this person.You want only the people a form captured, and not the ones who appeared straight in Stripe.
origin_detailThe secondary value of that first record, read according to created_via: a form id, an event name, or a plan name.You want the people acquired by one specific form, so you pass created_via=form and the form's id here.
first_touch_channelOne of the nine channel buckets (Organic Search, AI, Organic Social, Paid Search, Paid Social, Email, Direct, Referral, Non-attributed) — the bucket the person's first visit landed in.The channel report says AI sent you customers and you want to see who they are.
first_touch_sourceThe concrete source behind that channel — Google, LinkedIn, ChatGPT, Direct, and so on.AI traffic converts and you want to know whether it was ChatGPT or Claude that sent the customers.
paidtrue returns only people with a payment-bearing Stripe event; false returns only those without one.You want to email everyone who signed up but never paid.
has_emailtrue returns only rows with a non-empty email address.You are exporting a list for an email tool, and a row with no address is useless to you.
current_planPlan name from the person's most recent payment-bearing Stripe record.You are contacting everyone currently on the Starter plan about an upgrade.
range / from + toDate window on created_at — when the person first appeared.You want the people who arrived in July, not everyone you have ever seen.
limitRows per page (default 20, max 1000).You are walking the whole list and want fewer round trips.
starting_afterReturns the people after this cursor — pass back the next_cursor from the previous response.You are paging through the complete list: keep calling until has_more is false.

Cohort drilldowns — first_touch_page, scope, form_id

Two selectors answer "who is behind that number" for a page's or a form's customer count. They change the underlying source; they are not extra filters layered on the usual people list.

ParameterWhat it doesWhen you'd use it
first_touch_pageSwitches the list to the payers whose first-touch landing page was this page. Reconciles with page-report's customer counts for the same window. Pass the page exactly as page-report returns it.A blog post shows 12 customers in the page report and you want to see those 12 people.
scopeApplies to first_touch_page only: new (default) counts people whose first-ever payment falls inside the range; renew counts existing customers who charged again inside it.The page report's number looks too good, and you want to know how much of it is genuinely new business.
form_idSwitches the list to the payers who submitted this form. Reconciles with form-report's revenue for the same window. Pass the id exactly as form-report returns it.A demo form is credited with revenue and you want the names of the people who filled it in.

Things to know:

  • The two are mutually exclusive. They select different cohorts, so passing both is a 400, never a silent precedence rule.
  • Rows change shape. A drilldown returns that source's columns (e.g. revenue_in_range_cents and first_sub_date for a page cohort; submit_count and lifetime_revenue_cents for a form cohort), not the standard person row.
  • A drilldown is a single page of results. Neither source supports cursor pagination, so starting_after with a drilldown is a 400 rather than a silently ignored parameter, and the response always carries has_more: false / next_cursor: null. Raise limit (max 1000) instead.
  • form_id is a reach cohort. A payer who submitted N forms appears under all N — never sum across forms.
  • Both sources need customer:read, the same scope /people already requires, so the rows stay behind the same gate.

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.

Blocks exist on the single form only, because the list form is one cursor-paginated person list with no separable sections:

BlockSingle-form keyWhat it costs
revenuerevenuethe lifetime-revenue summary (one filtered timeline read)
acquisitionacquisitionthe acquiring campaign — an unfiltered scan of paid-acquired customers, the single most expensive read on this endpoint
?q=jane@acme.com&fields=revenue      → the person row + `revenue`, no campaign scan
?fields=revenue                      → 400 — the list form has no blocks

Rules that bite:

  • FullVision resolves the legal set AFTER form selection, not per endpoint. Same route, two answers: FullVision honours fields with q and rejects it without it, because omitting q picks the list form, which declares no blocks.
  • Empty, duplicated, unknown, and * are all 400s — fields=, fields=revenue,revenue, fields=nope, fields=*. A stray comma is a typo, not a request for everything. The error message lists the legal blocks inline.
  • Trimming does not guarantee a query saved per block. The contract is correct trimming — you get exactly the blocks you asked for and nothing else — never one fewer round trip per block dropped. It happens to buy a real saving here, but do not budget on that shape holding everywhere.
  • person and journey_id are never droppable — they are the addressing result, not a block.
  • fields never lowers the scopes the endpoint requires. ?fields=revenue still needs customer:read, and the rows still carry personal data.

Authorization

AuthorizationBearer <token>

API key as Bearer token

In: header

Query Parameters

q?string

Exact identifier lookup — email, cus_* id, person/visitor UUID, or external id. Omit for the group form.

limit?integer

Group form only: number of ranked rows to return (default 20, max 1000). Ignored when the addressing key is supplied.

Range1 <= value <= 1000
starting_after?string

Return rows after this cursor (from next_cursor).

first_touch_page?string

Group form: the people behind one landing page's customer count — pass the cleaned page exactly as page-report returns it. Pair with scope. Mutually exclusive with form_id.

scope?string

With first_touch_page: new (first-ever payment inside the range, default) or renew (existing customer who charged again inside it).

Value in

  • "new"
  • "renew"
form_id?string

Group form: the payers who submitted this form (reach cohort). A payer who submitted N forms appears under all N — never sum across forms. Mutually exclusive with first_touch_page.

created_via?string

Creation-mode bucket of the person's earliest record.

Value in

  • "stripe"
  • "form"
  • "events"
origin_detail?string

Secondary value of the first-touch record, scoped by created_via.

first_touch_channel?string

First-touch channel bucket.

Value in

  • "Organic Search"
  • "AI"
  • "Organic Social"
  • "Paid Search"
  • "Paid Social"
  • "Email"
  • "Direct"
  • "Referral"
  • "Non-attributed"
first_touch_source?string

Granular classified first-touch source.

paid?string

true = has a payment-bearing Stripe event.

Value in

  • "true"
  • "false"
has_email?string

true = the person has a non-empty email.

Value in

  • "true"
  • "false"
current_plan?string

Plan name of the latest payment-bearing Stripe record.

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.

Responses

One person (q supplied) or the criteria-filtered person cohort

get/people
curl -X GET "https://data.fullvision.io/people?limit=20&created_via=form&paid=true"
Example Responses
{  "person": {},  "journey_id": "string",  "revenue": {    "lifetime_revenue_cents": 0,    "charge_count": 0,    "first_charge_at": "string",    "last_charge_at": "string"  },  "acquisition": {},  "is_cached": true,  "age_s": 0,  "last_refresh": "string"}
application/json