Find the pages that create pipeline
The job: know which pages bring in people who turn into revenue, as distinct from pages that bring in traffic. They are rarely the same pages, and the difference is where your content budget should go.
Who this is for: content and demand-gen teams who can see pageviews in analytics and cannot see what those pageviews were worth.
Before you start
- You need an API key (
sk_sig_…) — mint one under Exports & API in the dashboard. The plaintext is shown exactly once; store it asSIGNAL_API_KEY. - You need the pixel live on your site, or there are no visitors to read. The quickstart covers install and verification.
- You need a connected store, for anything touching revenue or orders. Without one those endpoints return empty rather than failing, which is easy to misread as "no sales".
The one call
curl -s "https://app.signal.geysera.com/signal-api/v1/attribution?days=90" \
-H "Authorization: Bearer $SIGNAL_API_KEY"
{
"window_days": 90,
"has_data": true,
"computed_at": "2026-09-15T06:47:31Z",
"totals": {
"visitors": 38104,
"visitors_total": 41220,
"identified": 21068,
"identify_rate": 55.3,
"high_intent": 3310,
"high_intent_rate": 15.7,
"intent_threshold": 60,
"touch_coverage_pct": 31.2,
"median_hours_to_identify": 0.7,
"channels_active": 9
},
"sources": [
{
"source_medium": "google / organic",
"channel": "Organic Search",
"visitors": 12044,
"identified": 7210,
"identify_rate": 59.9,
"high_intent": 1180,
"avg_intent": 41.2
}
],
"landing_pages": [
{
"path": "/pricing",
"visitors": 2140,
"identified": 1388,
"identify_rate": 64.9,
"high_intent": 402,
"avg_intent": 58.3
}
],
"channels": [ "..." ],
"campaigns": [ "..." ],
"first_vs_last": [ "..." ],
"trend": [ "..." ],
"revenue": { "...": "..." }
}
Read touch_coverage_pct before you read anything else
It is the share of visitors whose first touch we can actually see. When it is low — and it often is — every source and landing-page number below it describes that minority, not your traffic. Attribution built on a third of your visitors is still useful for comparing sources, and it is not a number to put in a board deck as a total.
The single biggest cause is that the pixel misses the landing pageview itself on a meaningful share of sessions, so the visitor's first recorded page is not the page they arrived on.
The comparison worth making
Traffic rank and quality rank are different lists. This is the job, so do it on
landing_pages — the pages themselves, not the channels that fed them:
a = get("/attribution", days=90)
pages = [p for p in a["landing_pages"] if p["visitors"] >= 200] # ignore thin rows
by_traffic = sorted(pages, key=lambda p: p["visitors"], reverse=True)
by_quality = sorted(pages, key=lambda p: p["high_intent"] / p["visitors"], reverse=True)
The visitors >= 200 floor is not optional. Without it the top of your quality
list is a page with four visitors, two of whom were interested.
The interesting output is the disagreement, not either list. A page near the
top of by_traffic and the bottom of by_quality is doing real work for your
rankings and none for your pipeline — that is a page to stop investing in, or
to re-point at a different audience. The reverse — small traffic, high intent —
is usually the best content you have and nobody is sending anyone to it.
traffic_rank = {p["path"]: i for i, p in enumerate(by_traffic)}
quality_rank = {p["path"]: i for i, p in enumerate(by_quality)}
gaps = sorted(pages, key=lambda p: quality_rank[p["path"]] - traffic_rank[p["path"]])
# gaps[0] : most under-rated — deserves more traffic than it gets
# gaps[-1] : most over-rated — brings visitors who do not become anything
The same two lists on a["sources"] answer a different and also useful
question: which acquisition channels bring people worth having. Pages tell you
what to write; sources tell you where to spend. They are not substitutes, and
the page list is the one this workflow is named after.
avg_intent is None, not 0, when nobody scored
A page whose visitors were never identified has no intent to average, and the field says so rather than reporting a zero that would sort it alongside a page full of genuinely uninterested people. Filter before you sort:
scored = [p for p in pages if p["avg_intent"] is not None]
These two fields are new, and you may need to ask for a fresh answer
high_intent and avg_intent were added to landing_pages in September 2026.
Before that the grouping published volume only, which is why ranking pages by
quality was not possible.
The response is normally served from a nightly rollup — cached: true, with
computed_at telling you when. A rollup computed before the change does not
carry the new fields, so for one cycle either code defensively:
if p.get("high_intent") is None:
... # rollup predates the field; re-check after tonight's run
There is no live recompute on this endpoint, and that is deliberate. This
page used to show ?refresh=true; the parameter does not exist here, and like
any unknown query parameter it is ignored — so a script that sent it got a
cached answer while believing it had forced a fresh one.
A live recompute reads up to 200,000 visitor rows. Behind an API key that is a trivially repeatable way to exhaust the database, and an integrator polling on a cron would do it without meaning to. The dashboard has the live path because a person clicking Refresh is self-limiting; a script is not.
Read computed_at instead. It tells you exactly how fresh the answer is, so
staleness is visible rather than guessed at:
a = get("/attribution", days=90)
age_hours = (now() - parse(a["computed_at"])).total_seconds() / 3600
Asking for the analysis
Here is 90 days of acquisition data:
{attribution_json}
touch_coverage_pct is {n}. State clearly what that means for the confidence of
everything you are about to say.
Then, from landing_pages: which three pages bring visitors who become high
intent, and which three bring volume that does not? Use high_intent relative to
visitors, not raw counts. Ignore any page with fewer than 200 visitors and say
you ignored it. Name any page that ranks far higher by traffic than by quality,
and any that ranks far higher by quality than by traffic — those two lists are
the answer. Do not recommend a content or budget change you cannot support with
a number from this payload.
What will go wrong
has_data: false. Two different things, and the response tells you which.
If available_windows is present, you asked for a window that has no rollup —
the commonest cause by far, and nothing is wrong with your data. The field
lists the windows that do exist; ask again with one of those. days accepts
any value from 1 to 365, but only the rolled-up windows return figures.
If available_windows is absent, the window exists and nothing was captured
for it: a new workspace, or one whose pixel has been down. Then it is empty
rather than wrong — check the flag rather than reading zeros as a finding.
Self-referral as first touch. A large share of unattributed visitors have your own domain as their first recorded referrer. That is the pixel missing the entry, not people arriving from nowhere.
computed_at is not now. The rollup runs nightly. If you are looking for
the effect of something you shipped this morning, it is not in here yet, and
there is no way to force it from the API — see above for why. Wait for the next
rollup, or look at the dashboard, which does have a live path.
totals.truncated: true. The answer was built from part of your traffic
because the read hit its row cap. The numbers are still internally consistent,
so nothing looks wrong — which is
exactly why the flag exists. Do not compare a truncated response against a
cached one and call the difference a trend.
Next: turning content into pipeline · a weekly revenue brief your LLM writes
Markdown source: /developers/workflows/find-the-pages-that-create-pipeline.md