API reference
Read API
Read overview, timeseries, breakdowns, revenue, profit, cohorts and path to purchase with a read-scoped key, the same numbers as the dashboard.
The Read API returns the numbers behind the dashboard: traffic, revenue, profit, cohorts and path to purchase. It uses the same queries as the dashboard, so a figure from the API matches the figure on screen for the same range and filters.
Authentication
Create a key with the read scope in Settings → API keys, then send it as a Bearer token. Read keys are on the Starter plan and above. See Authentication and API keys.
curl "https://api.productlense.app/api/v1/stats/overview?site_id=SITE_ID&from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $PL_READ_KEY"A read key can read; it cannot send events or payments, and it cannot change anything.
Endpoints
| Endpoint | Returns |
|---|---|
GET /api/v1/stats/overview | Visitors, pageviews, sessions, bounce rate, average duration, signups, customers, revenue, fees, net revenue, spend, profit, CAC, ROAS, conversion rate, revenue per visitor and profit per visitor |
GET /api/v1/stats/timeseries | Points { t, v } for one metric: metric=visitors, pageviews, revenue_usd, profit_usd or signups, with interval=hour, day, week or month |
GET /api/v1/stats/breakdown | Rows for one dimension: dim=channel, source, medium, campaign, term, content, ref_domain, path, entry_path, exit_path, hostname, country, region, city, device, browser, os or event, with limit and offset |
GET /api/v1/stats/revenue | MRR, new, expansion and churned MRR, refunds, revenue, fees, net revenue, customers, ARPU and LTV |
GET /api/v1/stats/profit | One row per channel, source, medium and campaign with customers, revenue, fees, net revenue, spend, profit, CAC, ROAS and payback |
GET /api/v1/stats/cohorts | Net revenue retention cohorts, by=channel, source or campaign |
GET /api/v1/stats/purchase-path | Days and visits to purchase, a weekday × hour heatmap of payments and revenue by device. |
profit, cohorts and purchase-path need a plan that includes those reports (Growth and above); on a smaller plan they answer 403 with the code plan_required.
Parameters
| Parameter | Required | Meaning |
|---|---|---|
site_id | yes | The site id from Settings → Sites. The key must belong to this site. |
from, to | yes | Dates as YYYY-MM-DD, inclusive, within your plan's history window |
tz | no | An IANA time zone; defaults to the site's reporting time zone |
filters | no | key:operator:value triples separated by commas, such as channel:is:paid,path:contains:/blog. |
Responses
Every response uses one envelope:
{ "ok": true, "data": { "visitors": 18240, "revenue_usd": 2407, "fees_usd": 152, "spend_usd": 1518, "profit_usd": 737 }, "meta": {} }Amounts are USD at the payment date's ECB rate, with the definitions in How ProductLense counts. Unique visitor counts carry the approximation stated there. Errors use the shape on Errors.
Rate limit
Each key can make 120 requests a minute. Past that, the API answers 429 with the code rate_limited and a Retry-After header in seconds.
Example
const params = new URLSearchParams({ site_id: 'SITE_ID', from: '2026-09-01', to: '2026-09-30', dim: 'channel', limit: '10' });
const res = await fetch(`https://api.productlense.app/api/v1/stats/breakdown?${params}`, {
headers: { Authorization: `Bearer ${process.env.PL_READ_KEY}` },
});
const { ok, data, error } = await res.json();
if (!ok) throw new Error(`${error.code}: ${error.message}`);import os
import requests
res = requests.get(
"https://api.productlense.app/api/v1/stats/breakdown",
params={"site_id": "SITE_ID", "from": "2026-09-01", "to": "2026-09-30", "dim": "channel", "limit": 10},
headers={"Authorization": f"Bearer {os.environ['PL_READ_KEY']}"},
timeout=10,
)
body = res.json()
if not body["ok"]:
raise RuntimeError(body["error"]["code"])To ask questions in plain language from Claude, Cursor or ChatGPT instead, see Connect an AI assistant.